@nforma.ai/nforma 0.2.1 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/agents/{qgsd-codebase-mapper.md → nf-codebase-mapper.md} +1 -1
- package/agents/{qgsd-debugger.md → nf-debugger.md} +3 -3
- package/agents/{qgsd-executor.md → nf-executor.md} +14 -14
- package/agents/{qgsd-integration-checker.md → nf-integration-checker.md} +1 -1
- package/agents/{qgsd-phase-researcher.md → nf-phase-researcher.md} +6 -6
- package/agents/{qgsd-plan-checker.md → nf-plan-checker.md} +9 -9
- package/agents/{qgsd-planner.md → nf-planner.md} +9 -9
- package/agents/{qgsd-project-researcher.md → nf-project-researcher.md} +2 -2
- package/agents/{qgsd-quorum-orchestrator.md → nf-quorum-orchestrator.md} +33 -33
- package/agents/{qgsd-quorum-slot-worker.md → nf-quorum-slot-worker.md} +3 -3
- package/agents/{qgsd-quorum-synthesizer.md → nf-quorum-synthesizer.md} +3 -3
- package/agents/{qgsd-quorum-test-worker.md → nf-quorum-test-worker.md} +1 -1
- package/agents/{qgsd-quorum-worker.md → nf-quorum-worker.md} +6 -6
- package/agents/{qgsd-research-synthesizer.md → nf-research-synthesizer.md} +5 -5
- package/agents/{qgsd-roadmapper.md → nf-roadmapper.md} +3 -3
- package/agents/{qgsd-verifier.md → nf-verifier.md} +8 -8
- package/bin/accept-debug-invariant.cjs +2 -2
- package/bin/account-manager.cjs +10 -10
- package/bin/aggregate-requirements.cjs +1 -1
- package/bin/analyze-assumptions.cjs +3 -3
- package/bin/analyze-state-space.cjs +14 -14
- package/bin/assumption-register.cjs +146 -0
- package/bin/attribute-trace-divergence.cjs +1 -1
- package/bin/auth-drivers/gh-cli.cjs +1 -1
- package/bin/auth-drivers/pool.cjs +1 -1
- package/bin/autoClosePtoF.cjs +3 -3
- package/bin/budget-tracker.cjs +77 -0
- package/bin/build-layer-manifest.cjs +153 -0
- package/bin/call-quorum-slot.cjs +3 -3
- package/bin/ccr-secure-config.cjs +5 -5
- package/bin/check-bundled-sdks.cjs +1 -1
- package/bin/check-mcp-health.cjs +1 -1
- package/bin/check-provider-health.cjs +6 -6
- package/bin/check-spec-sync.cjs +26 -26
- package/bin/check-trace-schema-drift.cjs +5 -5
- package/bin/conformance-schema.cjs +2 -2
- package/bin/cross-layer-dashboard.cjs +297 -0
- package/bin/design-impact.cjs +377 -0
- package/bin/detect-coverage-gaps.cjs +7 -7
- package/bin/failure-mode-catalog.cjs +227 -0
- package/bin/failure-taxonomy.cjs +177 -0
- package/bin/formal-scope-scan.cjs +179 -0
- package/bin/gate-a-grounding.cjs +334 -0
- package/bin/gate-b-abstraction.cjs +243 -0
- package/bin/gate-c-validation.cjs +166 -0
- package/bin/generate-formal-specs.cjs +17 -17
- package/bin/generate-petri-net.cjs +3 -3
- package/bin/generate-tla-cfg.cjs +5 -5
- package/bin/git-heatmap.cjs +571 -0
- package/bin/harness-diagnostic.cjs +326 -0
- package/bin/hazard-model.cjs +261 -0
- package/bin/install-formal-tools.cjs +1 -1
- package/bin/install.js +184 -139
- package/bin/instrumentation-map.cjs +178 -0
- package/bin/invariant-catalog.cjs +437 -0
- package/bin/issue-classifier.cjs +2 -2
- package/bin/load-baseline-requirements.cjs +4 -4
- package/bin/manage-agents-core.cjs +32 -32
- package/bin/migrate-to-slots.cjs +39 -39
- package/bin/mismatch-register.cjs +217 -0
- package/bin/nForma.cjs +176 -81
- package/bin/{qgsd-solve.cjs → nf-solve.cjs} +327 -14
- package/bin/observe-config.cjs +8 -0
- package/bin/observe-debt-writer.cjs +1 -1
- package/bin/observe-handler-deps.cjs +356 -0
- package/bin/observe-handler-grafana.cjs +2 -17
- package/bin/observe-handler-internal.cjs +5 -5
- package/bin/observe-handler-logstash.cjs +2 -17
- package/bin/observe-handler-prometheus.cjs +2 -17
- package/bin/observe-handler-upstream.cjs +251 -0
- package/bin/observe-handlers.cjs +12 -33
- package/bin/observe-render.cjs +68 -22
- package/bin/observe-utils.cjs +37 -0
- package/bin/observed-fsm.cjs +324 -0
- package/bin/planning-paths.cjs +6 -0
- package/bin/polyrepo.cjs +1 -1
- package/bin/probe-quorum-slots.cjs +1 -1
- package/bin/promote-gate-maturity.cjs +274 -0
- package/bin/promote-model.cjs +1 -1
- package/bin/propose-debug-invariants.cjs +1 -1
- package/bin/quorum-cache.cjs +144 -0
- package/bin/quorum-consensus-gate.cjs +1 -1
- package/bin/quorum-slot-dispatch.cjs +6 -6
- package/bin/requirements-core.cjs +1 -1
- package/bin/review-mcp-logs.cjs +1 -1
- package/bin/risk-heatmap.cjs +151 -0
- package/bin/run-account-manager-tlc.cjs +4 -4
- package/bin/run-account-pool-alloy.cjs +2 -2
- package/bin/run-alloy.cjs +2 -2
- package/bin/run-audit-alloy.cjs +2 -2
- package/bin/run-breaker-tlc.cjs +3 -3
- package/bin/run-formal-check.cjs +9 -9
- package/bin/run-formal-verify.cjs +30 -9
- package/bin/run-installer-alloy.cjs +2 -2
- package/bin/run-oscillation-tlc.cjs +4 -4
- package/bin/run-phase-tlc.cjs +1 -1
- package/bin/run-protocol-tlc.cjs +4 -4
- package/bin/run-quorum-composition-alloy.cjs +2 -2
- package/bin/run-sensitivity-sweep.cjs +2 -2
- package/bin/run-stop-hook-tlc.cjs +3 -3
- package/bin/run-tlc.cjs +21 -21
- package/bin/run-transcript-alloy.cjs +2 -2
- package/bin/secrets.cjs +5 -5
- package/bin/security-sweep.cjs +238 -0
- package/bin/sensitivity-report.cjs +3 -3
- package/bin/set-secret.cjs +5 -5
- package/bin/setup-telemetry-cron.sh +3 -3
- package/bin/stall-detector.cjs +126 -0
- package/bin/state-candidates.cjs +206 -0
- package/bin/sync-baseline-requirements.cjs +1 -1
- package/bin/telemetry-collector.cjs +1 -1
- package/bin/test-changed.cjs +111 -0
- package/bin/test-recipe-gen.cjs +250 -0
- package/bin/trace-corpus-stats.cjs +211 -0
- package/bin/unified-mcp-server.mjs +3 -3
- package/bin/update-scoreboard.cjs +1 -1
- package/bin/validate-memory.cjs +2 -2
- package/bin/validate-traces.cjs +10 -10
- package/bin/verify-quorum-health.cjs +66 -5
- package/bin/xstate-to-tla.cjs +4 -4
- package/bin/xstate-trace-walker.cjs +3 -3
- package/commands/{qgsd → nf}/add-phase.md +3 -3
- package/commands/{qgsd → nf}/add-requirement.md +3 -3
- package/commands/{qgsd → nf}/add-todo.md +3 -3
- package/commands/{qgsd → nf}/audit-milestone.md +4 -4
- package/commands/{qgsd → nf}/check-todos.md +3 -3
- package/commands/{qgsd → nf}/cleanup.md +3 -3
- package/commands/{qgsd → nf}/close-formal-gaps.md +2 -2
- package/commands/{qgsd → nf}/complete-milestone.md +9 -9
- package/commands/{qgsd → nf}/debug.md +9 -9
- package/commands/{qgsd → nf}/discuss-phase.md +3 -3
- package/commands/{qgsd → nf}/execute-phase.md +15 -15
- package/commands/{qgsd → nf}/fix-tests.md +3 -3
- package/commands/{qgsd → nf}/formal-test-sync.md +1 -1
- package/commands/{qgsd → nf}/health.md +3 -3
- package/commands/{qgsd → nf}/help.md +3 -3
- package/commands/{qgsd → nf}/insert-phase.md +3 -3
- package/commands/nf/join-discord.md +18 -0
- package/commands/{qgsd → nf}/list-phase-assumptions.md +2 -2
- package/commands/{qgsd → nf}/map-codebase.md +7 -7
- package/commands/{qgsd → nf}/map-requirements.md +3 -3
- package/commands/{qgsd → nf}/mcp-restart.md +3 -3
- package/commands/{qgsd → nf}/mcp-set-model.md +8 -8
- package/commands/{qgsd → nf}/mcp-setup.md +63 -63
- package/commands/{qgsd → nf}/mcp-status.md +3 -3
- package/commands/{qgsd → nf}/mcp-update.md +7 -7
- package/commands/{qgsd → nf}/new-milestone.md +8 -8
- package/commands/{qgsd → nf}/new-project.md +8 -8
- package/commands/{qgsd → nf}/observe.md +49 -16
- package/commands/{qgsd → nf}/pause-work.md +3 -3
- package/commands/{qgsd → nf}/plan-milestone-gaps.md +5 -5
- package/commands/{qgsd → nf}/plan-phase.md +6 -6
- package/commands/{qgsd → nf}/polyrepo.md +2 -2
- package/commands/{qgsd → nf}/progress.md +3 -3
- package/commands/{qgsd → nf}/queue.md +2 -2
- package/commands/{qgsd → nf}/quick.md +8 -8
- package/commands/{qgsd → nf}/quorum-test.md +10 -10
- package/commands/{qgsd → nf}/quorum.md +40 -40
- package/commands/{qgsd → nf}/reapply-patches.md +2 -2
- package/commands/{qgsd → nf}/remove-phase.md +3 -3
- package/commands/{qgsd → nf}/research-phase.md +12 -12
- package/commands/{qgsd → nf}/resume-work.md +3 -3
- package/commands/nf/review-requirements.md +31 -0
- package/commands/{qgsd → nf}/set-profile.md +3 -3
- package/commands/{qgsd → nf}/settings.md +6 -6
- package/commands/{qgsd → nf}/solve.md +35 -35
- package/commands/{qgsd → nf}/sync-baselines.md +4 -4
- package/commands/{qgsd → nf}/triage.md +10 -10
- package/commands/{qgsd → nf}/update.md +3 -3
- package/commands/{qgsd → nf}/verify-work.md +5 -5
- package/hooks/dist/config-loader.js +188 -32
- package/hooks/dist/conformance-schema.cjs +2 -2
- package/hooks/dist/gsd-context-monitor.js +118 -13
- package/hooks/dist/{qgsd-check-update.js → nf-check-update.js} +5 -5
- package/hooks/dist/{qgsd-circuit-breaker.js → nf-circuit-breaker.js} +35 -24
- package/hooks/dist/nf-circuit-breaker.test.js +1002 -0
- package/hooks/dist/{qgsd-precompact.js → nf-precompact.js} +13 -13
- package/hooks/dist/nf-precompact.test.js +227 -0
- package/hooks/dist/{qgsd-prompt.js → nf-prompt.js} +110 -33
- package/hooks/dist/nf-prompt.test.js +698 -0
- package/hooks/dist/nf-session-start.js +185 -0
- package/hooks/dist/nf-session-start.test.js +354 -0
- package/hooks/dist/{qgsd-slot-correlator.js → nf-slot-correlator.js} +13 -5
- package/hooks/dist/nf-slot-correlator.test.js +85 -0
- package/hooks/dist/{qgsd-spec-regen.js → nf-spec-regen.js} +17 -8
- package/hooks/dist/nf-spec-regen.test.js +73 -0
- package/hooks/dist/{qgsd-statusline.js → nf-statusline.js} +12 -3
- package/hooks/dist/nf-statusline.test.js +157 -0
- package/hooks/dist/{qgsd-stop.js → nf-stop.js} +152 -18
- package/hooks/dist/nf-stop.test.js +1388 -0
- package/hooks/dist/{qgsd-token-collector.js → nf-token-collector.js} +12 -4
- package/hooks/dist/nf-token-collector.test.js +262 -0
- package/hooks/dist/unified-mcp-server.mjs +2 -2
- package/package.json +4 -4
- package/scripts/build-hooks.js +13 -6
- package/scripts/secret-audit.sh +1 -1
- package/scripts/verify-hooks-sync.cjs +90 -0
- package/templates/{qgsd.json → nf.json} +4 -4
- package/commands/qgsd/join-discord.md +0 -18
- package/hooks/dist/qgsd-session-start.js +0 -122
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
// bin/run-installer-alloy.cjs
|
|
4
|
-
// Invokes Alloy 6 JAR headless for
|
|
4
|
+
// Invokes Alloy 6 JAR headless for nForma installer and taxonomy specs (GAP-7, GAP-8).
|
|
5
5
|
// Requirements: GAP-7, GAP-8
|
|
6
6
|
//
|
|
7
7
|
// Usage:
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// - .planning/formal/alloy/org.alloytools.alloy.dist.jar (see VERIFICATION_TOOLS.md for download)
|
|
17
17
|
|
|
18
18
|
const { spawnSync } = require('child_process');
|
|
19
|
-
const JAVA_HEAP_MAX = process.env.
|
|
19
|
+
const JAVA_HEAP_MAX = process.env.NF_JAVA_HEAP_MAX || '512m';
|
|
20
20
|
const fs = require('fs');
|
|
21
21
|
const path = require('path');
|
|
22
22
|
const { writeCheckResult } = require('./write-check-result.cjs');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
// bin/run-oscillation-tlc.cjs
|
|
4
|
-
// Invokes TLC model checker for
|
|
4
|
+
// Invokes TLC model checker for nForma oscillation and convergence TLA+ specifications.
|
|
5
5
|
// Requirements: GAP-1, GAP-5
|
|
6
6
|
//
|
|
7
7
|
// Usage:
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// - .planning/formal/tla/tla2tools.jar (see .planning/formal/tla/README.md for download command)
|
|
15
15
|
|
|
16
16
|
const { spawnSync } = require('child_process');
|
|
17
|
-
const JAVA_HEAP_MAX = process.env.
|
|
17
|
+
const JAVA_HEAP_MAX = process.env.NF_JAVA_HEAP_MAX || '512m';
|
|
18
18
|
const fs = require('fs');
|
|
19
19
|
const path = require('path');
|
|
20
20
|
const { writeCheckResult } = require('./write-check-result.cjs');
|
|
@@ -129,8 +129,8 @@ if (!fs.existsSync(jarPath)) {
|
|
|
129
129
|
|
|
130
130
|
// ── 4. Resolve spec and config paths ─────────────────────────────────────────
|
|
131
131
|
const specFileName = configName === 'MCoscillation'
|
|
132
|
-
? '
|
|
133
|
-
: '
|
|
132
|
+
? 'NFOscillation.tla'
|
|
133
|
+
: 'NFConvergence.tla';
|
|
134
134
|
const specPath = path.join(ROOT, '.planning', 'formal', 'tla', specFileName);
|
|
135
135
|
const cfgPath = path.join(ROOT, '.planning', 'formal', 'tla', configName + '.cfg');
|
|
136
136
|
|
package/bin/run-phase-tlc.cjs
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// NOTE: Uses spawnSync (no shell) for safe subprocess invocation -- no exec().
|
|
16
16
|
|
|
17
17
|
const { spawnSync } = require('child_process');
|
|
18
|
-
const JAVA_HEAP_MAX = process.env.
|
|
18
|
+
const JAVA_HEAP_MAX = process.env.NF_JAVA_HEAP_MAX || '512m';
|
|
19
19
|
const fs = require('fs');
|
|
20
20
|
const path = require('path');
|
|
21
21
|
|
package/bin/run-protocol-tlc.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
// bin/run-protocol-tlc.cjs
|
|
4
|
-
// Invokes TLC model checker for
|
|
4
|
+
// Invokes TLC model checker for nForma protocol termination TLA+ specifications.
|
|
5
5
|
// Requirements: GAP-2, GAP-6
|
|
6
6
|
//
|
|
7
7
|
// Usage:
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// - .planning/formal/tla/tla2tools.jar (see .planning/formal/tla/README.md for download command)
|
|
15
15
|
|
|
16
16
|
const { spawnSync } = require('child_process');
|
|
17
|
-
const JAVA_HEAP_MAX = process.env.
|
|
17
|
+
const JAVA_HEAP_MAX = process.env.NF_JAVA_HEAP_MAX || '512m';
|
|
18
18
|
const fs = require('fs');
|
|
19
19
|
const path = require('path');
|
|
20
20
|
const { writeCheckResult } = require('./write-check-result.cjs');
|
|
@@ -129,8 +129,8 @@ if (!fs.existsSync(jarPath)) {
|
|
|
129
129
|
|
|
130
130
|
// ── 4. Resolve spec and config paths ─────────────────────────────────────────
|
|
131
131
|
const specFileName = configName === 'MCdeliberation'
|
|
132
|
-
? '
|
|
133
|
-
: '
|
|
132
|
+
? 'NFDeliberation.tla'
|
|
133
|
+
: 'NFPreFilter.tla';
|
|
134
134
|
const specPath = path.join(ROOT, '.planning', 'formal', 'tla', specFileName);
|
|
135
135
|
const cfgPath = path.join(ROOT, '.planning', 'formal', 'tla', configName + '.cfg');
|
|
136
136
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
// bin/run-quorum-composition-alloy.cjs
|
|
4
|
-
// Invokes Alloy 6 JAR headless for the
|
|
4
|
+
// Invokes Alloy 6 JAR headless for the nForma quorum composition model.
|
|
5
5
|
// Requirements: SPEC-03
|
|
6
6
|
//
|
|
7
7
|
// Usage:
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// - .planning/formal/alloy/org.alloytools.alloy.dist.jar (see VERIFICATION_TOOLS.md for download)
|
|
13
13
|
|
|
14
14
|
const { spawnSync } = require('child_process');
|
|
15
|
-
const JAVA_HEAP_MAX = process.env.
|
|
15
|
+
const JAVA_HEAP_MAX = process.env.NF_JAVA_HEAP_MAX || '512m';
|
|
16
16
|
const fs = require('fs');
|
|
17
17
|
const path = require('path');
|
|
18
18
|
const { writeCheckResult } = require('./write-check-result.cjs');
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// Always exits 0.
|
|
14
14
|
|
|
15
15
|
const { spawnSync } = require('child_process');
|
|
16
|
-
const JAVA_HEAP_MAX = process.env.
|
|
16
|
+
const JAVA_HEAP_MAX = process.env.NF_JAVA_HEAP_MAX || '512m';
|
|
17
17
|
const fs = require('fs');
|
|
18
18
|
const path = require('path');
|
|
19
19
|
const os = require('os');
|
|
@@ -67,7 +67,7 @@ function runTLCSweep(maxSize) {
|
|
|
67
67
|
const overrideCfg = baseCfg.replace(/MaxSize\s*=\s*\d+/, 'MaxSize = ' + maxSize);
|
|
68
68
|
fs.writeFileSync(tmpCfg, overrideCfg, 'utf8');
|
|
69
69
|
|
|
70
|
-
const tlaFile = path.join(__dirname, '..', '.planning', 'formal', 'tla', '
|
|
70
|
+
const tlaFile = path.join(__dirname, '..', '.planning', 'formal', 'tla', 'NFQuorum.tla');
|
|
71
71
|
process.stderr.write('[heap] Xms=64m Xmx=' + JAVA_HEAP_MAX + '\n');
|
|
72
72
|
const javaResult = spawnSync('java', [
|
|
73
73
|
'-Xms64m', '-Xmx' + JAVA_HEAP_MAX,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
// bin/run-stop-hook-tlc.cjs
|
|
4
|
-
// Invokes TLC model checker for the
|
|
4
|
+
// Invokes TLC model checker for the nForma Stop hook TLA+ specification.
|
|
5
5
|
// Requirements: SPEC-01
|
|
6
6
|
//
|
|
7
7
|
// Usage:
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// - .planning/formal/tla/tla2tools.jar (see .planning/formal/tla/README.md for download command)
|
|
14
14
|
|
|
15
15
|
const { spawnSync } = require('child_process');
|
|
16
|
-
const JAVA_HEAP_MAX = process.env.
|
|
16
|
+
const JAVA_HEAP_MAX = process.env.NF_JAVA_HEAP_MAX || '512m';
|
|
17
17
|
const fs = require('fs');
|
|
18
18
|
const path = require('path');
|
|
19
19
|
const { writeCheckResult } = require('./write-check-result.cjs');
|
|
@@ -118,7 +118,7 @@ if (!fs.existsSync(jarPath)) {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
// ── 4. Resolve spec and config paths ─────────────────────────────────────────
|
|
121
|
-
const specPath = path.join(ROOT, '.planning', 'formal', 'tla', '
|
|
121
|
+
const specPath = path.join(ROOT, '.planning', 'formal', 'tla', 'NFStopHook.tla');
|
|
122
122
|
const cfgPath = path.join(ROOT, '.planning', 'formal', 'tla', configName + '.cfg');
|
|
123
123
|
|
|
124
124
|
// LivenessProperty1/2/3 requires -workers 1 (TLC multi-worker liveness bug in v1.8.0)
|
package/bin/run-tlc.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
// bin/run-tlc.cjs
|
|
4
|
-
// Invokes TLC model checker for the
|
|
4
|
+
// Invokes TLC model checker for the nForma formal TLA+ specification.
|
|
5
5
|
// Requirements: TLA-04
|
|
6
6
|
//
|
|
7
7
|
// Usage:
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// - .planning/formal/tla/tla2tools.jar (see .planning/formal/tla/README.md for download command)
|
|
15
15
|
|
|
16
16
|
const { spawnSync } = require('child_process');
|
|
17
|
-
const JAVA_HEAP_MAX = process.env.
|
|
17
|
+
const JAVA_HEAP_MAX = process.env.NF_JAVA_HEAP_MAX || '512m';
|
|
18
18
|
const fs = require('fs');
|
|
19
19
|
const path = require('path');
|
|
20
20
|
const { writeCheckResult } = require('./write-check-result.cjs');
|
|
@@ -298,52 +298,52 @@ if (require.main === module) {
|
|
|
298
298
|
// Map config names to their corresponding spec files.
|
|
299
299
|
// Static map for known exceptions; auto-discovery handles the rest.
|
|
300
300
|
const SPEC_MAP = {
|
|
301
|
-
'MCMCPEnv': '
|
|
302
|
-
'MCsafety': '
|
|
303
|
-
'MCliveness': '
|
|
304
|
-
'
|
|
305
|
-
'MCrecruiting-liveness': '
|
|
306
|
-
'MCrecruiting-safety': '
|
|
301
|
+
'MCMCPEnv': 'NFMCPEnv.tla',
|
|
302
|
+
'MCsafety': 'NFQuorum.tla',
|
|
303
|
+
'MCliveness': 'NFQuorum.tla',
|
|
304
|
+
'MCNFQuorum': 'NFQuorum_xstate.tla',
|
|
305
|
+
'MCrecruiting-liveness': 'NFRecruiting.tla',
|
|
306
|
+
'MCrecruiting-safety': 'NFRecruiting.tla',
|
|
307
307
|
'MCTUINavigation': 'TUINavigation.tla',
|
|
308
308
|
};
|
|
309
309
|
|
|
310
|
-
// Auto-discover spec file: (1) check SPEC_MAP, (2) scan cfg header for
|
|
311
|
-
// (3) try naming conventions, (4) fall back to
|
|
310
|
+
// Auto-discover spec file: (1) check SPEC_MAP, (2) scan cfg header for nForma*.tla ref,
|
|
311
|
+
// (3) try naming conventions, (4) fall back to NFQuorum.tla
|
|
312
312
|
function resolveSpecFile(cfgName) {
|
|
313
313
|
if (SPEC_MAP[cfgName]) return SPEC_MAP[cfgName];
|
|
314
314
|
|
|
315
315
|
const tlaDir = path.join(ROOT, '.planning', 'formal', 'tla');
|
|
316
316
|
|
|
317
|
-
// Strategy 1: read cfg header for
|
|
317
|
+
// Strategy 1: read cfg header for nForma*.tla reference (with or without .tla suffix)
|
|
318
318
|
try {
|
|
319
319
|
const cfgContent = fs.readFileSync(path.join(tlaDir, cfgName + '.cfg'), 'utf8');
|
|
320
320
|
const headerLines = cfgContent.split('\n').slice(0, 5).join('\n');
|
|
321
|
-
// Match
|
|
322
|
-
const refMatch = headerLines.match(/
|
|
321
|
+
// Match NF*.tla or "for nForma*." (without .tla extension)
|
|
322
|
+
const refMatch = headerLines.match(/NF\w+\.tla/) || headerLines.match(/NF\w+/);
|
|
323
323
|
if (refMatch) {
|
|
324
324
|
const candidate = refMatch[0].endsWith('.tla') ? refMatch[0] : refMatch[0] + '.tla';
|
|
325
325
|
if (fs.existsSync(path.join(tlaDir, candidate))) return candidate;
|
|
326
326
|
}
|
|
327
327
|
} catch (_) { /* fall through */ }
|
|
328
328
|
|
|
329
|
-
// Strategy 2: naming convention — strip MC prefix, normalize hyphens, find matching
|
|
329
|
+
// Strategy 2: naming convention — strip MC prefix, normalize hyphens, find matching NF*.tla
|
|
330
330
|
const stripped = cfgName.replace(/^MC/, '').toLowerCase().replace(/-/g, '');
|
|
331
331
|
try {
|
|
332
332
|
const allTla = fs.readdirSync(tlaDir).filter(f => f.endsWith('.tla') && !f.includes('TTrace'));
|
|
333
|
-
const
|
|
333
|
+
const nfFiles = allTla.filter(f => f.startsWith('NF'));
|
|
334
334
|
const normalize = (s) => s.toLowerCase().replace(/-/g, '');
|
|
335
|
-
// Exact match against
|
|
336
|
-
const match =
|
|
335
|
+
// Exact match against nForma-prefixed files
|
|
336
|
+
const match = nfFiles.find(f => normalize(f.replace('NF', '').replace('.tla', '')) === stripped);
|
|
337
337
|
if (match) return match;
|
|
338
|
-
// Fuzzy substring match against
|
|
339
|
-
const fuzzy =
|
|
338
|
+
// Fuzzy substring match against nForma-prefixed files
|
|
339
|
+
const fuzzy = nfFiles.find(f => normalize(f).includes(stripped));
|
|
340
340
|
if (fuzzy) return fuzzy;
|
|
341
|
-
// Fallback: check non-
|
|
341
|
+
// Fallback: check non-nForma-prefixed files (exact match on stripped name)
|
|
342
342
|
const nonPrefixed = allTla.find(f => normalize(f.replace('.tla', '')) === stripped);
|
|
343
343
|
if (nonPrefixed) return nonPrefixed;
|
|
344
344
|
} catch (_) { /* fall through */ }
|
|
345
345
|
|
|
346
|
-
return '
|
|
346
|
+
return 'NFQuorum.tla';
|
|
347
347
|
}
|
|
348
348
|
|
|
349
349
|
const specFile = resolveSpecFile(configName);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
// bin/run-transcript-alloy.cjs
|
|
4
|
-
// Invokes Alloy 6 JAR headless for
|
|
4
|
+
// Invokes Alloy 6 JAR headless for nForma transcript scanning spec (GAP-4).
|
|
5
5
|
// Requirements: GAP-4
|
|
6
6
|
//
|
|
7
7
|
// Usage:
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// - .planning/formal/alloy/org.alloytools.alloy.dist.jar (see VERIFICATION_TOOLS.md for download)
|
|
16
16
|
|
|
17
17
|
const { spawnSync } = require('child_process');
|
|
18
|
-
const JAVA_HEAP_MAX = process.env.
|
|
18
|
+
const JAVA_HEAP_MAX = process.env.NF_JAVA_HEAP_MAX || '512m';
|
|
19
19
|
const fs = require('fs');
|
|
20
20
|
const path = require('path');
|
|
21
21
|
const { writeCheckResult } = require('./write-check-result.cjs');
|
package/bin/secrets.cjs
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
const os = require('os');
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
const SERVICE = '
|
|
5
|
+
const SERVICE = 'nforma';
|
|
6
6
|
|
|
7
|
-
const INDEX_PATH = path.join(os.homedir(), '.claude', '
|
|
7
|
+
const INDEX_PATH = path.join(os.homedir(), '.claude', 'nf-key-index.json');
|
|
8
8
|
|
|
9
9
|
// Read the key index (no keychain access needed — just a JSON file)
|
|
10
10
|
function readIndex() {
|
|
@@ -84,7 +84,7 @@ async function syncToClaudeJson(service) {
|
|
|
84
84
|
try {
|
|
85
85
|
credentials = await list(service);
|
|
86
86
|
} catch (e) {
|
|
87
|
-
process.stderr.write('[
|
|
87
|
+
process.stderr.write('[nf-secrets] keytar unavailable: ' + e.message + '\n');
|
|
88
88
|
return;
|
|
89
89
|
}
|
|
90
90
|
|
|
@@ -100,7 +100,7 @@ async function syncToClaudeJson(service) {
|
|
|
100
100
|
try {
|
|
101
101
|
raw = fs.readFileSync(claudeJsonPath, 'utf8');
|
|
102
102
|
} catch (e) {
|
|
103
|
-
process.stderr.write('[
|
|
103
|
+
process.stderr.write('[nf-secrets] ~/.claude.json not found, skipping sync\n');
|
|
104
104
|
return;
|
|
105
105
|
}
|
|
106
106
|
|
|
@@ -108,7 +108,7 @@ async function syncToClaudeJson(service) {
|
|
|
108
108
|
try {
|
|
109
109
|
claudeJson = JSON.parse(raw);
|
|
110
110
|
} catch (e) {
|
|
111
|
-
process.stderr.write('[
|
|
111
|
+
process.stderr.write('[nf-secrets] ~/.claude.json is invalid JSON, skipping sync\n');
|
|
112
112
|
return;
|
|
113
113
|
}
|
|
114
114
|
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* bin/security-sweep.cjs — Standalone security scanner for nForma.
|
|
6
|
+
*
|
|
7
|
+
* Scans git-tracked files for hardcoded secrets, debug artifacts, and API keys.
|
|
8
|
+
* Produces structured findings with file:line references for VERIFICATION.md.
|
|
9
|
+
*
|
|
10
|
+
* Advisory only — exit code 0 always. Never blocks.
|
|
11
|
+
*
|
|
12
|
+
* Exports: SECRET_PATTERNS, scanFile, scanDirectory, formatReport
|
|
13
|
+
* CLI: node bin/security-sweep.cjs [--json] [--cwd /path]
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
// spawnSync is used (not exec) to avoid shell injection — arguments are passed as an array
|
|
19
|
+
const { spawnSync } = require('child_process');
|
|
20
|
+
|
|
21
|
+
// ─── Secret Patterns ────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
const SECRET_PATTERNS = [
|
|
24
|
+
{ name: 'AWS Access Key', pattern: /AKIA[A-Z0-9]{16}/, severity: 'high' },
|
|
25
|
+
{ name: 'GitHub Token', pattern: /gh[ps]_[A-Za-z0-9_]{36,}/, severity: 'high' },
|
|
26
|
+
{ name: 'GitHub PAT', pattern: /github_pat_[A-Za-z0-9_]{22,}/, severity: 'high' },
|
|
27
|
+
{ name: 'Stripe Live Key', pattern: /[sr]k_live_[A-Za-z0-9]{20,}/, severity: 'high' },
|
|
28
|
+
{ name: 'OpenAI Key', pattern: /sk-[A-Za-z0-9]{32,}/, severity: 'high' },
|
|
29
|
+
{ name: 'Generic API Key Assignment', pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*['"][^'"]{10,}['"]/i, severity: 'medium' },
|
|
30
|
+
{ name: 'Generic Secret Assignment', pattern: /(?:secret|password)\s*[:=]\s*['"][^'"]{8,}['"]/i, severity: 'medium' },
|
|
31
|
+
{ name: 'Debugger Statement', pattern: /^\s*debugger\s*;?\s*$/, severity: 'low' },
|
|
32
|
+
{ name: 'Sensitive Console.log', pattern: /console\.log\(.*(?:password|secret|token|key|api_key)/i, severity: 'low' },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
// Words that indicate a line is a test fixture / not a real secret
|
|
36
|
+
const TEST_INDICATOR_WORDS = ['test', 'mock', 'fake', 'example', 'dummy', 'fixture', 'placeholder', 'todo'];
|
|
37
|
+
|
|
38
|
+
// File patterns to exclude from scanning
|
|
39
|
+
const EXCLUDE_PATTERNS = [
|
|
40
|
+
/\.test\./,
|
|
41
|
+
/\.spec\./,
|
|
42
|
+
/security-sweep\.cjs$/,
|
|
43
|
+
/\.md$/,
|
|
44
|
+
/\.json$/,
|
|
45
|
+
/\.jsonl$/,
|
|
46
|
+
/node_modules\//,
|
|
47
|
+
/\.planning\/\.quorum-cache\//,
|
|
48
|
+
/package-lock\.json$/,
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
// ─── scanFile ────────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Scan file content line-by-line against SECRET_PATTERNS.
|
|
55
|
+
* @param {string} filePath - Relative or absolute file path (for reporting).
|
|
56
|
+
* @param {string} content - File content to scan.
|
|
57
|
+
* @returns {Array<{file:string, line:number, column:number, pattern_name:string, severity:string, match:string}>}
|
|
58
|
+
*/
|
|
59
|
+
function scanFile(filePath, content) {
|
|
60
|
+
if (!content || typeof content !== 'string') return [];
|
|
61
|
+
|
|
62
|
+
// Skip binary content (null bytes in first 512 chars)
|
|
63
|
+
if (content.slice(0, 512).includes('\0')) return [];
|
|
64
|
+
|
|
65
|
+
const findings = [];
|
|
66
|
+
const lines = content.split('\n');
|
|
67
|
+
|
|
68
|
+
for (let i = 0; i < lines.length; i++) {
|
|
69
|
+
const line = lines[i];
|
|
70
|
+
const lineLower = line.toLowerCase();
|
|
71
|
+
|
|
72
|
+
// Skip lines containing test indicator words
|
|
73
|
+
if (TEST_INDICATOR_WORDS.some(w => lineLower.includes(w))) continue;
|
|
74
|
+
|
|
75
|
+
for (const pat of SECRET_PATTERNS) {
|
|
76
|
+
const m = pat.pattern.exec(line);
|
|
77
|
+
if (m) {
|
|
78
|
+
findings.push({
|
|
79
|
+
file: filePath,
|
|
80
|
+
line: i + 1,
|
|
81
|
+
column: m.index + 1,
|
|
82
|
+
pattern_name: pat.name,
|
|
83
|
+
severity: pat.severity,
|
|
84
|
+
match: m[0].length > 40 ? m[0].slice(0, 37) + '...' : m[0],
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return findings;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ─── scanDirectory ───────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Scan git-tracked files in a directory.
|
|
97
|
+
* @param {string} cwd - Working directory to scan.
|
|
98
|
+
* @param {Object} [options]
|
|
99
|
+
* @param {string[]} [options.excludePatterns] - Additional glob patterns to exclude.
|
|
100
|
+
* @param {number} [options.maxFiles=500] - Max files to scan.
|
|
101
|
+
* @returns {{findings: Array, files_scanned: number, duration_ms: number}}
|
|
102
|
+
*/
|
|
103
|
+
function scanDirectory(cwd, options = {}) {
|
|
104
|
+
const maxFiles = options.maxFiles || 500;
|
|
105
|
+
const startTime = Date.now();
|
|
106
|
+
|
|
107
|
+
// Get tracked files via git ls-files (spawnSync, not exec — no shell injection)
|
|
108
|
+
let files = [];
|
|
109
|
+
try {
|
|
110
|
+
const result = spawnSync('git', ['ls-files'], {
|
|
111
|
+
cwd,
|
|
112
|
+
encoding: 'utf8',
|
|
113
|
+
timeout: 5000,
|
|
114
|
+
});
|
|
115
|
+
if (result.status === 0 && result.stdout) {
|
|
116
|
+
files = result.stdout.split('\n').filter(f => f.trim());
|
|
117
|
+
}
|
|
118
|
+
} catch (_) {
|
|
119
|
+
return { findings: [], files_scanned: 0, duration_ms: Date.now() - startTime };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Filter out excluded patterns
|
|
123
|
+
files = files.filter(f => !EXCLUDE_PATTERNS.some(rx => rx.test(f)));
|
|
124
|
+
|
|
125
|
+
// Apply additional exclude patterns from options
|
|
126
|
+
if (options.excludePatterns && Array.isArray(options.excludePatterns)) {
|
|
127
|
+
for (const pat of options.excludePatterns) {
|
|
128
|
+
try {
|
|
129
|
+
const rx = new RegExp(pat);
|
|
130
|
+
files = files.filter(f => !rx.test(f));
|
|
131
|
+
} catch (_) {}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Cap file count
|
|
136
|
+
if (files.length > maxFiles) {
|
|
137
|
+
files = files.slice(0, maxFiles);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const allFindings = [];
|
|
141
|
+
let scanned = 0;
|
|
142
|
+
|
|
143
|
+
for (const relPath of files) {
|
|
144
|
+
const absPath = path.join(cwd, relPath);
|
|
145
|
+
try {
|
|
146
|
+
const content = fs.readFileSync(absPath, 'utf8');
|
|
147
|
+
// Skip binary files (null bytes in first 512 bytes)
|
|
148
|
+
if (content.slice(0, 512).includes('\0')) continue;
|
|
149
|
+
scanned++;
|
|
150
|
+
const findings = scanFile(relPath, content);
|
|
151
|
+
allFindings.push(...findings);
|
|
152
|
+
} catch (_) {
|
|
153
|
+
// Skip unreadable files silently
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
findings: allFindings,
|
|
159
|
+
files_scanned: scanned,
|
|
160
|
+
duration_ms: Date.now() - startTime,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ─── formatReport ────────────────────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Format scan results as a markdown section for VERIFICATION.md.
|
|
168
|
+
* @param {{findings: Array, files_scanned: number, duration_ms: number}} scanResult
|
|
169
|
+
* @returns {string}
|
|
170
|
+
*/
|
|
171
|
+
function formatReport(scanResult) {
|
|
172
|
+
const { findings, files_scanned, duration_ms } = scanResult;
|
|
173
|
+
const lines = [];
|
|
174
|
+
|
|
175
|
+
lines.push('## Security Sweep');
|
|
176
|
+
lines.push('');
|
|
177
|
+
lines.push(`**Scanned:** ${files_scanned} files in ${duration_ms}ms`);
|
|
178
|
+
|
|
179
|
+
if (!findings || findings.length === 0) {
|
|
180
|
+
lines.push('**Findings:** 0');
|
|
181
|
+
lines.push('');
|
|
182
|
+
lines.push('No hardcoded secrets, debug artifacts, or API keys detected.');
|
|
183
|
+
return lines.join('\n');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const high = findings.filter(f => f.severity === 'high').length;
|
|
187
|
+
const medium = findings.filter(f => f.severity === 'medium').length;
|
|
188
|
+
const low = findings.filter(f => f.severity === 'low').length;
|
|
189
|
+
|
|
190
|
+
lines.push(`**Findings:** ${findings.length} (${high} high, ${medium} medium, ${low} low)`);
|
|
191
|
+
lines.push('');
|
|
192
|
+
lines.push('| Severity | File | Line | Pattern | Match |');
|
|
193
|
+
lines.push('|----------|------|------|---------|-------|');
|
|
194
|
+
|
|
195
|
+
for (const f of findings) {
|
|
196
|
+
lines.push(`| ${f.severity} | ${f.file} | ${f.line} | ${f.pattern_name} | ${f.match} |`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
lines.push('');
|
|
200
|
+
lines.push('_Advisory: Review findings and confirm whether they are genuine secrets or false positives._');
|
|
201
|
+
|
|
202
|
+
return lines.join('\n');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ─── CLI ─────────────────────────────────────────────────────────────────────
|
|
206
|
+
|
|
207
|
+
if (require.main === module) {
|
|
208
|
+
const args = process.argv.slice(2);
|
|
209
|
+
const jsonFlag = args.includes('--json');
|
|
210
|
+
const cwdIdx = args.indexOf('--cwd');
|
|
211
|
+
const cwd = cwdIdx >= 0 && args[cwdIdx + 1] ? args[cwdIdx + 1] : process.cwd();
|
|
212
|
+
|
|
213
|
+
const result = scanDirectory(cwd);
|
|
214
|
+
|
|
215
|
+
if (jsonFlag) {
|
|
216
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
217
|
+
} else {
|
|
218
|
+
process.stdout.write(formatReport(result) + '\n');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Log conformance event (best-effort)
|
|
222
|
+
try {
|
|
223
|
+
const planningPaths = require('./planning-paths.cjs');
|
|
224
|
+
const conformancePath = planningPaths.resolveWithFallback(cwd, 'conformance-events');
|
|
225
|
+
const event = {
|
|
226
|
+
ts: new Date().toISOString(),
|
|
227
|
+
action: 'security_sweep',
|
|
228
|
+
files_scanned: result.files_scanned,
|
|
229
|
+
findings_count: result.findings.length,
|
|
230
|
+
duration_ms: result.duration_ms,
|
|
231
|
+
};
|
|
232
|
+
fs.appendFileSync(conformancePath, JSON.stringify(event) + '\n');
|
|
233
|
+
} catch (_) {}
|
|
234
|
+
|
|
235
|
+
process.exit(0);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
module.exports = { SECRET_PATTERNS, scanFile, scanDirectory, formatReport };
|
|
@@ -22,7 +22,7 @@ const REPORT_MD_PATH = process.env.SENSITIVITY_MD_PATH ||
|
|
|
22
22
|
|
|
23
23
|
const PARAM_ANNOTATIONS = {
|
|
24
24
|
MaxSize: {
|
|
25
|
-
codePath: 'hooks/
|
|
25
|
+
codePath: 'hooks/nf-prompt.js FAN_OUT_COUNT; .planning/formal/tla/MCsafety.cfg MaxSize',
|
|
26
26
|
testCases: [
|
|
27
27
|
'Test quorum at N=2 boundary: set FAN_OUT_COUNT=2 in providers.json and run quorum round',
|
|
28
28
|
'Test quorum at N=1 (no quorum): verify workflow rejects insufficient available slots',
|
|
@@ -44,7 +44,7 @@ const PARAM_ANNOTATIONS = {
|
|
|
44
44
|
],
|
|
45
45
|
},
|
|
46
46
|
MaxDeliberation: {
|
|
47
|
-
codePath: '.planning/formal/tla/MCsafety.cfg MaxDeliberation=7; src/machines/
|
|
47
|
+
codePath: '.planning/formal/tla/MCsafety.cfg MaxDeliberation=7; src/machines/nf-workflow.machine.ts MaxDeliberation guard',
|
|
48
48
|
testCases: [
|
|
49
49
|
'Test quorum workflow with max deliberation rounds reached — verify DECIDED fallback',
|
|
50
50
|
'Test rapid-fire slot responses (all within 1 deliberation round)',
|
|
@@ -89,7 +89,7 @@ function parseNDJSON(filePath) {
|
|
|
89
89
|
function generateReport(records) {
|
|
90
90
|
const now = new Date().toISOString();
|
|
91
91
|
const lines = [
|
|
92
|
-
'# Sensitivity Report —
|
|
92
|
+
'# Sensitivity Report — nForma v0.20',
|
|
93
93
|
'',
|
|
94
94
|
'Generated: ' + now,
|
|
95
95
|
'Source: .planning/formal/sensitivity-report.ndjson (' + records.length + ' records)',
|
package/bin/set-secret.cjs
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* set-secret.cjs
|
|
5
5
|
* Usage: node bin/set-secret.cjs <KEY_NAME> <value>
|
|
6
6
|
*
|
|
7
|
-
* Stores KEY_NAME=value in the OS keychain under service "
|
|
8
|
-
* then syncs all
|
|
7
|
+
* Stores KEY_NAME=value in the OS keychain under service "nforma",
|
|
8
|
+
* then syncs all nforma secrets into ~/.claude.json mcpServers env blocks.
|
|
9
9
|
*/
|
|
10
10
|
const { set, syncToClaudeJson, SERVICE } = require('./secrets.cjs');
|
|
11
11
|
|
|
@@ -19,11 +19,11 @@ const value = valueParts.join(' ');
|
|
|
19
19
|
(async () => {
|
|
20
20
|
try {
|
|
21
21
|
await set(SERVICE, keyName, value);
|
|
22
|
-
console.log(`[
|
|
22
|
+
console.log(`[nf] Stored ${keyName} in keychain (service: ${SERVICE})`);
|
|
23
23
|
await syncToClaudeJson(SERVICE);
|
|
24
|
-
console.log('[
|
|
24
|
+
console.log('[nf] Synced keychain secrets to ~/.claude.json');
|
|
25
25
|
} catch (e) {
|
|
26
|
-
console.error('[
|
|
26
|
+
console.error('[nf] Error:', e.message);
|
|
27
27
|
process.exit(1);
|
|
28
28
|
}
|
|
29
29
|
})();
|
|
@@ -26,11 +26,11 @@ if crontab -l 2>/dev/null | grep -q "telemetry-collector"; then
|
|
|
26
26
|
fi
|
|
27
27
|
|
|
28
28
|
# Install cron entry: top of every hour
|
|
29
|
-
(crontab -l 2>/dev/null; echo "0 * * * * $CRON_CMD >> /tmp/
|
|
29
|
+
(crontab -l 2>/dev/null; echo "0 * * * * $CRON_CMD >> /tmp/nf-telemetry.log 2>&1") | crontab -
|
|
30
30
|
|
|
31
31
|
echo "Telemetry cron installed."
|
|
32
32
|
|
|
33
33
|
# Windows: use Task Scheduler. Create a Basic Task that runs:
|
|
34
|
-
# node C:\path\to\
|
|
35
|
-
# followed by: node C:\path\to\
|
|
34
|
+
# node C:\path\to\nforma\bin\telemetry-collector.cjs
|
|
35
|
+
# followed by: node C:\path\to\nforma\bin\issue-classifier.cjs
|
|
36
36
|
# Trigger: Daily, repeat every 1 hour indefinitely.
|