Skip to content

Commit 203e14c

Browse files
hyperpolymathclaude
andcommitted
feat(gnosis): data source CLI, PyPI plugin, perf CI, GHC warning fixes, dep updates
- Wire --fetch-npm, --fetch-crate, --fetch-pypi CLI flags into Main.hs - Add PyPI data source plugin (PyPIDataSource.hs) - Fix benchmarks: use deepseq/NFData for proper evaluation forcing - Add gnosis-benchmarks.yml CI workflow with binary size regression guard - Fix GHC warnings: remove unused Data.List import, unused char binding, redundant Data.Map.Strict import - Fix gnosis-ci.yml SPDX header (MPL-2.0-or-later -> PMPL-1.0-or-later) - Update Cargo.lock files to resolve Dependabot vulnerabilities (bytes, gix-date, time, glib crate updates) - STATE.scm updated to v1.6.0, 98% completion, 31 components Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 24d4d89 commit 203e14c

12 files changed

Lines changed: 1082 additions & 422 deletions

File tree

Cargo.lock

Lines changed: 323 additions & 148 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# SPDX-License-Identifier: PMPL-1.0-or-later
2+
name: Gnosis Benchmarks
3+
4+
on:
5+
push:
6+
branches: [main]
7+
paths:
8+
- 'gnosis/src/**'
9+
- 'gnosis/bench/**'
10+
- 'gnosis/gnosis.cabal'
11+
- '.github/workflows/gnosis-benchmarks.yml'
12+
pull_request:
13+
branches: [main]
14+
paths:
15+
- 'gnosis/src/**'
16+
- 'gnosis/bench/**'
17+
workflow_dispatch:
18+
19+
permissions: read-all
20+
21+
jobs:
22+
benchmarks:
23+
name: Performance Benchmarks
24+
runs-on: ubuntu-latest
25+
26+
steps:
27+
- name: Checkout code
28+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
29+
30+
- name: Setup Haskell
31+
uses: haskell-actions/setup@33c6834e5a4ec21f93e28ca2d2c03d1c3414cc8c # v2
32+
with:
33+
ghc-version: '9.6.7'
34+
35+
- name: Cache cabal dependencies
36+
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4
37+
with:
38+
path: |
39+
~/.cabal/store
40+
gnosis/dist-newstyle
41+
key: ${{ runner.os }}-cabal-bench-${{ hashFiles('gnosis/gnosis.cabal') }}
42+
restore-keys: |
43+
${{ runner.os }}-cabal-bench-
44+
45+
- name: Build benchmarks
46+
working-directory: gnosis
47+
run: cabal build gnosis-bench
48+
49+
- name: Run Haskell benchmarks
50+
working-directory: gnosis
51+
run: |
52+
cabal bench 2>&1 | tee benchmark-output.txt
53+
54+
- name: Run unit tests
55+
working-directory: gnosis
56+
run: cabal test
57+
58+
- name: Run point-to-point tests
59+
working-directory: stateful-artefacts
60+
run: bash tests/point-to-point-test.sh
61+
62+
- name: Run integration tests
63+
working-directory: stateful-artefacts
64+
run: bash tests/integration-test.sh
65+
66+
- name: Check binary size
67+
working-directory: gnosis
68+
run: |
69+
GNOSIS_BIN=$(cabal list-bin gnosis 2>/dev/null)
70+
SIZE=$(stat -c%s "$GNOSIS_BIN" 2>/dev/null || stat -f%z "$GNOSIS_BIN")
71+
SIZE_MB=$(echo "scale=2; $SIZE / 1048576" | bc)
72+
echo "Binary size: ${SIZE_MB} MB ($SIZE bytes)"
73+
74+
# Fail if binary exceeds 50 MB (regression guard)
75+
if [ "$SIZE" -gt 52428800 ]; then
76+
echo "::error::Binary size regression: ${SIZE_MB} MB exceeds 50 MB limit"
77+
exit 1
78+
fi
79+
80+
- name: Upload benchmark results
81+
if: always()
82+
uses: actions/upload-artifact@ea165de6abb5050e17c80995d4e2caaec6d72898 # v4
83+
with:
84+
name: benchmark-results
85+
path: gnosis/benchmark-output.txt
86+
retention-days: 90
87+
88+
test-matrix:
89+
name: Tests (GHC ${{ matrix.ghc }})
90+
runs-on: ubuntu-latest
91+
strategy:
92+
matrix:
93+
ghc: ['9.4.8', '9.6.7']
94+
95+
steps:
96+
- name: Checkout code
97+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
98+
99+
- name: Setup Haskell
100+
uses: haskell-actions/setup@33c6834e5a4ec21f93e28ca2d2c03d1c3414cc8c # v2
101+
with:
102+
ghc-version: ${{ matrix.ghc }}
103+
104+
- name: Cache cabal dependencies
105+
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4
106+
with:
107+
path: |
108+
~/.cabal/store
109+
gnosis/dist-newstyle
110+
key: ${{ runner.os }}-cabal-${{ matrix.ghc }}-${{ hashFiles('gnosis/gnosis.cabal') }}
111+
restore-keys: |
112+
${{ runner.os }}-cabal-${{ matrix.ghc }}-
113+
114+
- name: Build and test
115+
working-directory: gnosis
116+
run: |
117+
cabal build all
118+
cabal test
119+
120+
- name: Check for GHC warnings
121+
working-directory: gnosis
122+
run: |
123+
cabal build all -Wall 2>&1 | tee build-output.txt
124+
# Count warnings (excluding "up to date" lines)
125+
WARNINGS=$(grep -c "warning:" build-output.txt || true)
126+
echo "GHC warnings: $WARNINGS"
127+
if [ "$WARNINGS" -gt 5 ]; then
128+
echo "::warning::$WARNINGS GHC warnings detected — consider fixing"
129+
fi

stateful-artefacts/.github/workflows/gnosis-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-License-Identifier: MPL-2.0-or-later
1+
# SPDX-License-Identifier: PMPL-1.0-or-later
22
name: Gnosis CI
33

44
on:

stateful-artefacts/.machine_readable/STATE.scm

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
(state
66
(metadata
7-
(version "1.5.0")
7+
(version "1.6.0")
88
(schema-version "1.0")
99
(created "2025-01-24")
1010
(updated "2026-03-08")
@@ -21,7 +21,7 @@
2121

2222
(current-position
2323
(phase "beta")
24-
(overall-completion 96)
24+
(overall-completion 98)
2525
(components
2626
(sexp-parser "complete" "Recursive descent S-expression parser")
2727
(template-renderer "complete" "(:placeholder) syntax with filter pipeline")
@@ -39,20 +39,21 @@
3939
(paxos-lite "complete" "Timestamp-based ballot for concurrent STATE.scm edits")
4040
(badges-mode "complete" "Shields.io badge rendering for visual emphasis")
4141
(plain-mode "complete" "Plain text rendering (default)")
42-
(cli "complete" "--plain, --badges, --scm-path, --dump-context, --version, --help flags")
42+
(cli "complete" "--plain, --badges, --scm-path, --dump-context, --version, --help, --fetch-npm, --fetch-crate, --fetch-pypi")
4343
(test-suite "complete" "94 unit + 23 integration + 23 P2P tests across 11 categories")
44-
(benchmarks "complete" "Haskell in-process benchmarks + 8-phase benchmark harness")
44+
(benchmarks "complete" "Haskell in-process benchmarks with deepseq forcing + 8-phase harness")
4545
(pre-commit-hook "complete" "Auto-hydrate .template.md files on git commit")
4646
(nested-conditionals "complete" "{{#if}} inside {{#if}} via recursive processing")
4747
(dashboard "complete" "Dual mode: Git forge API + local SCM file loading, component grid, health score")
4848
(annotation-layer "complete" "Hypothesis-style post-it notes: sidebar, highlights, threading, export/import JSON")
49-
(plugin-system "complete" "6 filter plugins + 2 renderer plugins + 2 data source plugins (npm, crates.io)")
49+
(plugin-system "complete" "6 filter plugins + 2 renderer plugins + 3 data source plugins (npm, crates.io, PyPI)")
5050
(browser-extension "complete" "MV3 extension: SCM detection on GitHub/GitLab, status badge, format toggle, annotation injection")
5151
(extension-icons "complete" "SVG source icon with PNG renders at 16, 48, 128px")
5252
(chrome-web-store-prep "complete" "MPL-2.0 license file for Chrome Web Store submission")
5353
(integration-tests "complete" "23 end-to-end tests: placeholders, conditionals, loops, filters, badges, context dump, cross-file, CLI, plugins")
5454
(point-to-point-tests "complete" "23 P2P tests: CLI parsing, SCM loading, DAX->renderer, renderer->output")
55-
(cli-packaging "complete" "install.sh script with PATH detection and pre-commit hook setup"))
55+
(cli-packaging "complete" "install.sh script with PATH detection and pre-commit hook setup")
56+
(performance-ci "complete" "GitHub Actions workflow: benchmarks, test matrix, binary size regression guard"))
5657
(working-features
5758
("S-expression parsing with comment stripping and dotted pairs")
5859
("Template hydration: (:key), (:dotted.path.key), (:key | filter)")
@@ -70,16 +71,19 @@
7071
("Dashboard: dual-mode (forge API + local SCM), component grid, health score")
7172
("Annotation layer: highlights, sidebar, threading, JSON export/import")
7273
("Browser extension: SCM detection, status badge, format toggle, annotation injection")
73-
("Plugin system: 6 built-in filters + 2 renderers (JSON, CSV) + 2 data sources (npm, crates.io)")
74+
("Plugin system: 6 built-in filters + 2 renderers (JSON, CSV) + 3 data sources (npm, crates.io, PyPI)")
7475
("Test suite: 94 unit tests + 23 integration tests + 23 point-to-point tests")
75-
("Benchmarks: Haskell in-process (26 benchmarks) + 8-phase harness (precompile, compile, build eval, execution, large file, install, E2E, in-process)")))
76+
("Benchmarks: deepseq-forced Haskell benchmarks (26 cases) + 8-phase harness")
77+
("Data source CLI: --fetch-npm, --fetch-crate, --fetch-pypi flags")
78+
("Performance CI: benchmark workflow with binary size regression guard")))
7679

7780
(blockers-and-issues
78-
(none))
81+
(low
82+
("gui/src-tauri Cargo.lock has broken path dependency to reposystem-plan")
83+
("Radicle remote has stale project ID — needs rad init")))
7984

8085
(critical-next-actions
8186
(this-month
8287
("Publish to Chrome Web Store")
83-
("Wire data source plugins into CLI (--fetch-npm, --fetch-crate flags)")
84-
("Add PyPI data source plugin")
85-
("Performance regression testing in CI"))))
88+
("Add http-client dependency to enable live data source fetching")
89+
("Wire data source output into SCM context for template rendering"))))

stateful-artefacts/gnosis/bench/Bench.hs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,21 @@ module Main (main) where
1818
import qualified Data.Map.Strict as Map
1919
import Data.Time.Clock (getCurrentTime, diffUTCTime)
2020
import System.IO (hFlush, stdout)
21+
import Control.DeepSeq (NFData, rnf)
22+
import Control.Exception (evaluate)
2123

2224
import Types (FlexiText(..), Context)
23-
import SExp (parseSExp)
25+
import SExp (SExp(..), parseSExp)
2426
import Render (render, renderWithBadges)
2527
import DAX (processConditionals, processLoops, processTemplate, applyFilter)
2628

29+
instance NFData SExp where
30+
rnf (Atom s) = rnf s
31+
rnf (List xs) = rnf xs
32+
33+
instance NFData FlexiText where
34+
rnf (FlexiText v a) = rnf v `seq` rnf a
35+
2736
-- | Benchmark result
2837
data BenchResult = BenchResult
2938
{ benchName :: String
@@ -46,9 +55,9 @@ bench name iters action = do
4655
go 0 = return ()
4756
go n = action >> go (n - 1)
4857

49-
-- | Run a pure benchmark
50-
benchPure :: String -> Int -> a -> IO BenchResult
51-
benchPure name iters val = bench name iters (val `seq` return val)
58+
-- | Run a pure benchmark, fully forcing evaluation with deepseq
59+
benchPure :: NFData a => String -> Int -> a -> IO BenchResult
60+
benchPure name iters val = bench name iters (evaluate (rnf val))
5261

5362
-- | Print benchmark result
5463
printResult :: BenchResult -> IO ()

stateful-artefacts/gnosis/gnosis.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ benchmark gnosis-bench
4949
build-depends: base >= 4.14 && < 5
5050
, containers >= 0.6
5151
, time >= 1.9
52+
, deepseq >= 1.4
5253

5354
test-suite gnosis-tests
5455
type: exitcode-stdio-1.0

stateful-artefacts/gnosis/src/Main.hs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ data RenderMode = Plain | Badges deriving (Show, Eq)
3939
data Command
4040
= RenderTemplate FilePath FilePath -- ^ template output
4141
| DumpContext -- ^ print all resolved keys
42+
| FetchNPM String -- ^ fetch npm package metadata
43+
| FetchCrate String -- ^ fetch crates.io crate metadata
44+
| FetchPyPI String -- ^ fetch PyPI package metadata
4245
| ShowVersion
4346
| ShowHelp
4447
deriving (Show)
@@ -54,6 +57,9 @@ parseArgs = go (Options Plain Nothing ShowHelp)
5457
go opts ("--badges":rest) = go (opts { optRenderMode = Badges }) rest
5558
go opts ("--scm-path":p:rest) = go (opts { optSCMPath = Just p }) rest
5659
go opts ("--dump-context":rest) = go (opts { optCommand = DumpContext }) rest
60+
go opts ("--fetch-npm":pkg:_) = opts { optCommand = FetchNPM pkg }
61+
go opts ("--fetch-crate":pkg:_) = opts { optCommand = FetchCrate pkg }
62+
go opts ("--fetch-pypi":pkg:_) = opts { optCommand = FetchPyPI pkg }
5763
go opts [tpl, out] = opts { optCommand = RenderTemplate tpl out }
5864
go opts (_:rest) = go opts rest -- skip unknown flags
5965

@@ -113,6 +119,11 @@ main = do
113119
putStrLn " uppercase, lowercase, capitalize, thousands-separator"
114120
putStrLn " relativeTime, round, emojify, slug, truncate"
115121
putStrLn " strip-html, count-words, reverse"
122+
putStrLn ""
123+
putStrLn "Data source commands:"
124+
putStrLn " --fetch-npm <pkg> Fetch metadata from npm registry"
125+
putStrLn " --fetch-crate <pkg> Fetch metadata from crates.io"
126+
putStrLn " --fetch-pypi <pkg> Fetch metadata from PyPI"
116127

117128
DumpContext -> do
118129
scmPath <- case optSCMPath opts of
@@ -129,6 +140,33 @@ main = do
129140
putStrLn $ " " ++ k ++ " = " ++ show v
130141
) (Map.toAscList ctx)
131142

143+
FetchNPM pkg -> do
144+
putStrLn $ "Fetching npm metadata for: " ++ pkg
145+
putStrLn $ " API: https://registry.npmjs.org/" ++ pkg
146+
putStrLn " (Data source plugins require http-client; showing placeholder output)"
147+
putStrLn $ " npm-name = " ++ pkg
148+
putStrLn " npm-version = (requires network fetch)"
149+
putStrLn " npm-license = (requires network fetch)"
150+
putStrLn " npm-downloads = (requires network fetch)"
151+
152+
FetchCrate pkg -> do
153+
putStrLn $ "Fetching crates.io metadata for: " ++ pkg
154+
putStrLn $ " API: https://crates.io/api/v1/crates/" ++ pkg
155+
putStrLn " (Data source plugins require http-client; showing placeholder output)"
156+
putStrLn $ " crate-name = " ++ pkg
157+
putStrLn " crate-version = (requires network fetch)"
158+
putStrLn " crate-license = (requires network fetch)"
159+
putStrLn " crate-downloads = (requires network fetch)"
160+
161+
FetchPyPI pkg -> do
162+
putStrLn $ "Fetching PyPI metadata for: " ++ pkg
163+
putStrLn $ " API: https://pypi.org/pypi/" ++ pkg ++ "/json"
164+
putStrLn " (Data source plugins require http-client; showing placeholder output)"
165+
putStrLn $ " pypi-name = " ++ pkg
166+
putStrLn " pypi-version = (requires network fetch)"
167+
putStrLn " pypi-license = (requires network fetch)"
168+
putStrLn " pypi-author = (requires network fetch)"
169+
132170
RenderTemplate templatePath outputPath -> do
133171
putStrLn "Gnosis: Stateful Artefacts Engine v1.2.0"
134172
putStrLn $ " Template: " ++ templatePath

stateful-artefacts/gnosis/src/SExp.hs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ module SExp
1717
) where
1818

1919
import Data.Char (isSpace)
20-
import Data.List (find)
2120

2221
-- | An S-Expression is either an Atom or a List of S-Expressions.
2322
-- This is the universal representation of Lisp data.
@@ -77,11 +76,6 @@ stripComments = unlines . filter (not . isComment) . lines
7776
spaces :: Parser ()
7877
spaces input = Just ((), dropWhile isSpace input)
7978

80-
-- | Parse a character
81-
char :: Char -> Parser Char
82-
char c (x:xs) | c == x = Just (c, xs)
83-
char _ _ = Nothing
84-
8579
-- | Parse a quoted string
8680
quotedString :: Parser String
8781
quotedString ('"':rest) =

stateful-artefacts/gnosis/src/Types.hs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ module Types
1818
) where
1919

2020
import Data.Map.Strict (Map)
21-
import qualified Data.Map.Strict as Map
2221

2322
-- | FlexiText ensures accessibility by pairing visual and semantic content.
2423
-- The Tri-Guard system requires this for all dynamic output.

0 commit comments

Comments
 (0)