@nforma.ai/nforma 0.2.1 → 0.29.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-preflight.cjs +89 -0
- 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 +36 -86
- 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/{qgsd-precompact.js → nf-precompact.js} +13 -13
- package/hooks/dist/{qgsd-prompt.js → nf-prompt.js} +110 -33
- package/hooks/dist/nf-session-start.js +185 -0
- package/hooks/dist/{qgsd-slot-correlator.js → nf-slot-correlator.js} +13 -5
- package/hooks/dist/{qgsd-spec-regen.js → nf-spec-regen.js} +17 -8
- package/hooks/dist/{qgsd-statusline.js → nf-statusline.js} +12 -3
- package/hooks/dist/{qgsd-stop.js → nf-stop.js} +152 -18
- package/hooks/dist/{qgsd-token-collector.js → nf-token-collector.js} +12 -4
- package/hooks/dist/unified-mcp-server.mjs +2 -2
- package/package.json +6 -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,13 +1,13 @@
|
|
|
1
|
-
<!-- DEPRECATED: This agent is superseded by
|
|
1
|
+
<!-- DEPRECATED: This agent is superseded by nf-quorum-slot-worker.md as of quick-101. Do not use. The orchestrator now spawns nf-quorum-slot-worker for all slot calls. This file is kept for reference only. -->
|
|
2
2
|
---
|
|
3
|
-
name:
|
|
3
|
+
name: nf-quorum-worker
|
|
4
4
|
description: Parallel quorum slot worker — spawned as a parallel Task by the orchestrator. Receives $ARGUMENTS with slot/round config, reads the repository, calls one slot via call-quorum-slot.cjs, and returns structured output.
|
|
5
5
|
tools: Read, Bash, Glob, Grep
|
|
6
6
|
color: blue
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
<role>
|
|
10
|
-
You are a
|
|
10
|
+
You are a nForma quorum slot worker. You are spawned as a parallel Task by the orchestrator — one worker per active quorum slot. Your sole job is to call one slot, capture the output, and return a structured result. You do NOT update any scoreboard. You do NOT write any files.
|
|
11
11
|
|
|
12
12
|
**Execution flow:**
|
|
13
13
|
|
|
@@ -20,7 +20,7 @@ You are a QGSD quorum slot worker. You are spawned as a parallel Task by the orc
|
|
|
20
20
|
4. Run the slot call via Bash (where `slot: <slotName>` comes from $ARGUMENTS):
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
node "$HOME/.claude/
|
|
23
|
+
node "$HOME/.claude/nf-bin/call-quorum-slot.cjs" --slot <slot> --timeout <timeout_ms> --cwd <repo_dir> <<'WORKER_PROMPT'
|
|
24
24
|
[constructed prompt — see Prompt Construction below]
|
|
25
25
|
WORKER_PROMPT
|
|
26
26
|
```
|
|
@@ -34,7 +34,7 @@ WORKER_PROMPT
|
|
|
34
34
|
|
|
35
35
|
Mode A prompt:
|
|
36
36
|
```
|
|
37
|
-
|
|
37
|
+
nForma Quorum — Round <round>
|
|
38
38
|
|
|
39
39
|
Repository: <repo_dir>
|
|
40
40
|
|
|
@@ -75,7 +75,7 @@ Evaluate this question independently. Give your honest answer with reasoning. Be
|
|
|
75
75
|
|
|
76
76
|
Mode B prompt:
|
|
77
77
|
```
|
|
78
|
-
|
|
78
|
+
nForma Quorum — Execution Review (Round <round>)
|
|
79
79
|
|
|
80
80
|
Repository: <repo_dir>
|
|
81
81
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: nf-research-synthesizer
|
|
3
3
|
description: Synthesizes research outputs from parallel researcher agents into SUMMARY.md. Spawned by /qgsd:new-project after 4 researcher agents complete.
|
|
4
4
|
tools: Read, Write, Bash
|
|
5
5
|
color: purple
|
|
@@ -27,7 +27,7 @@ If the prompt contains a `<files_to_read>` block, you MUST use the `Read` tool t
|
|
|
27
27
|
</role>
|
|
28
28
|
|
|
29
29
|
<downstream_consumer>
|
|
30
|
-
Your SUMMARY.md is consumed by the
|
|
30
|
+
Your SUMMARY.md is consumed by the nf-roadmapper agent which uses it to:
|
|
31
31
|
|
|
32
32
|
| Section | How Roadmapper Uses It |
|
|
33
33
|
|---------|------------------------|
|
|
@@ -122,7 +122,7 @@ Identify gaps that couldn't be resolved and need attention during planning.
|
|
|
122
122
|
|
|
123
123
|
## Step 6: Write SUMMARY.md
|
|
124
124
|
|
|
125
|
-
Use template: ~/.claude/
|
|
125
|
+
Use template: ~/.claude/nf/templates/research-project/SUMMARY.md
|
|
126
126
|
|
|
127
127
|
Write to `.planning/research/SUMMARY.md`
|
|
128
128
|
|
|
@@ -131,7 +131,7 @@ Write to `.planning/research/SUMMARY.md`
|
|
|
131
131
|
The 4 parallel researcher agents write files but do NOT commit. You commit everything together.
|
|
132
132
|
|
|
133
133
|
```bash
|
|
134
|
-
node ~/.claude/
|
|
134
|
+
node ~/.claude/nf/bin/gsd-tools.cjs commit "docs: complete project research" --files .planning/research/
|
|
135
135
|
```
|
|
136
136
|
|
|
137
137
|
## Step 8: Return Summary
|
|
@@ -142,7 +142,7 @@ Return brief confirmation with key points for the orchestrator.
|
|
|
142
142
|
|
|
143
143
|
<output_format>
|
|
144
144
|
|
|
145
|
-
Use template: ~/.claude/
|
|
145
|
+
Use template: ~/.claude/nf/templates/research-project/SUMMARY.md
|
|
146
146
|
|
|
147
147
|
Key sections:
|
|
148
148
|
- Executive Summary (2-3 paragraphs)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: nf-roadmapper
|
|
3
3
|
description: Creates project roadmaps with phase breakdown, requirement mapping, success criteria derivation, and coverage validation. Spawned by /qgsd:new-project orchestrator.
|
|
4
4
|
tools: Read, Write, Bash, Glob, Grep
|
|
5
5
|
color: purple
|
|
@@ -328,11 +328,11 @@ After roadmap creation, REQUIREMENTS.md gets updated with phase mappings:
|
|
|
328
328
|
| 2. Name | 0/2 | Not started | - |
|
|
329
329
|
```
|
|
330
330
|
|
|
331
|
-
Reference full template: `~/.claude/
|
|
331
|
+
Reference full template: `~/.claude/nf/templates/roadmap.md`
|
|
332
332
|
|
|
333
333
|
## STATE.md Structure
|
|
334
334
|
|
|
335
|
-
Use template from `~/.claude/
|
|
335
|
+
Use template from `~/.claude/nf/templates/state.md`.
|
|
336
336
|
|
|
337
337
|
Key sections:
|
|
338
338
|
- Project Reference (core value, current focus)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: nf-verifier
|
|
3
3
|
description: Verifies phase goal achievement through goal-backward analysis. Checks codebase delivers what phase promised, not just that tasks completed. Creates VERIFICATION.md report.
|
|
4
4
|
tools: Read, Write, Bash, Grep, Glob
|
|
5
5
|
color: green
|
|
@@ -57,7 +57,7 @@ Set `is_re_verification = false`, proceed with Step 1.
|
|
|
57
57
|
```bash
|
|
58
58
|
ls "$PHASE_DIR"/*-PLAN.md 2>/dev/null
|
|
59
59
|
ls "$PHASE_DIR"/*-SUMMARY.md 2>/dev/null
|
|
60
|
-
node ~/.claude/
|
|
60
|
+
node ~/.claude/nf/bin/gsd-tools.cjs roadmap get-phase "$PHASE_NUM"
|
|
61
61
|
grep -E "^| $PHASE_NUM" .planning/REQUIREMENTS.md 2>/dev/null
|
|
62
62
|
```
|
|
63
63
|
|
|
@@ -94,7 +94,7 @@ must_haves:
|
|
|
94
94
|
If no must_haves in frontmatter, check for Success Criteria:
|
|
95
95
|
|
|
96
96
|
```bash
|
|
97
|
-
PHASE_DATA=$(node ~/.claude/
|
|
97
|
+
PHASE_DATA=$(node ~/.claude/nf/bin/gsd-tools.cjs roadmap get-phase "$PHASE_NUM" --raw)
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
Parse the `success_criteria` array from the JSON output. If non-empty:
|
|
@@ -137,7 +137,7 @@ For each truth:
|
|
|
137
137
|
Use gsd-tools for artifact verification against must_haves in PLAN frontmatter:
|
|
138
138
|
|
|
139
139
|
```bash
|
|
140
|
-
ARTIFACT_RESULT=$(node ~/.claude/
|
|
140
|
+
ARTIFACT_RESULT=$(node ~/.claude/nf/bin/gsd-tools.cjs verify artifacts "$PLAN_PATH")
|
|
141
141
|
```
|
|
142
142
|
|
|
143
143
|
Parse JSON result: `{ all_passed, passed, total, artifacts: [{path, exists, issues, passed}] }`
|
|
@@ -186,7 +186,7 @@ Key links are critical connections. If broken, the goal fails even with all arti
|
|
|
186
186
|
Use gsd-tools for key link verification against must_haves in PLAN frontmatter:
|
|
187
187
|
|
|
188
188
|
```bash
|
|
189
|
-
LINKS_RESULT=$(node ~/.claude/
|
|
189
|
+
LINKS_RESULT=$(node ~/.claude/nf/bin/gsd-tools.cjs verify key-links "$PLAN_PATH")
|
|
190
190
|
```
|
|
191
191
|
|
|
192
192
|
Parse JSON result: `{ all_verified, verified, total, links: [{from, to, via, verified, detail}] }`
|
|
@@ -268,12 +268,12 @@ Identify files modified in this phase from SUMMARY.md key-files section, or extr
|
|
|
268
268
|
|
|
269
269
|
```bash
|
|
270
270
|
# Option 1: Extract from SUMMARY frontmatter
|
|
271
|
-
SUMMARY_FILES=$(node ~/.claude/
|
|
271
|
+
SUMMARY_FILES=$(node ~/.claude/nf/bin/gsd-tools.cjs summary-extract "$PHASE_DIR"/*-SUMMARY.md --fields key-files)
|
|
272
272
|
|
|
273
273
|
# Option 2: Verify commits exist (if commit hashes documented)
|
|
274
274
|
COMMIT_HASHES=$(grep -oE "[a-f0-9]{7,40}" "$PHASE_DIR"/*-SUMMARY.md | head -10)
|
|
275
275
|
if [ -n "$COMMIT_HASHES" ]; then
|
|
276
|
-
COMMITS_VALID=$(node ~/.claude/
|
|
276
|
+
COMMITS_VALID=$(node ~/.claude/nf/bin/gsd-tools.cjs verify commits $COMMIT_HASHES)
|
|
277
277
|
fi
|
|
278
278
|
|
|
279
279
|
# Fallback: grep for files
|
|
@@ -502,7 +502,7 @@ Omit this section entirely.
|
|
|
502
502
|
---
|
|
503
503
|
|
|
504
504
|
_Verified: {timestamp}_
|
|
505
|
-
_Verifier: Claude (
|
|
505
|
+
_Verifier: Claude (nf-verifier)_
|
|
506
506
|
```
|
|
507
507
|
|
|
508
508
|
## Return to Orchestrator
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Debug invariant write path (ARCH-03): writes a PROPERTY definition directly
|
|
5
5
|
// to a canonical .planning/formal/tla/ spec with full provenance tracking.
|
|
6
6
|
//
|
|
7
|
-
// When /
|
|
7
|
+
// When /nf:debug accepts a new invariant candidate, this script writes it to
|
|
8
8
|
// the canonical spec and records update_source=debug + session_id in model-registry.json.
|
|
9
9
|
//
|
|
10
10
|
// Usage:
|
|
@@ -31,7 +31,7 @@ function findProjectRoot(startPath) {
|
|
|
31
31
|
if (parent === current) break; // filesystem root
|
|
32
32
|
current = parent;
|
|
33
33
|
}
|
|
34
|
-
return path.join(__dirname, '..'); // fallback to
|
|
34
|
+
return path.join(__dirname, '..'); // fallback to nForma project root
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// ── Parse CLI arguments ───────────────────────────────────────────────────────
|
package/bin/account-manager.cjs
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* account-manager.cjs — OAuth account pool manager for
|
|
5
|
+
* account-manager.cjs — OAuth account pool manager for nForma providers
|
|
6
6
|
*
|
|
7
7
|
* Manages multiple OAuth credentials for providers with oauth_rotation config.
|
|
8
8
|
* The implementation is a state machine that directly mirrors
|
|
9
|
-
* .planning/formal/tla/
|
|
9
|
+
* .planning/formal/tla/NFAccountManager.tla — each TLA+ action is one FSM event.
|
|
10
10
|
*
|
|
11
11
|
* Usage:
|
|
12
12
|
* node bin/account-manager.cjs add --login [--name alias] [--provider gemini-1]
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* Credential layout (configurable via oauth_rotation in providers.json):
|
|
20
20
|
* active_file — ~/.gemini/oauth_creds.json (the live credential Gemini CLI reads)
|
|
21
21
|
* creds_dir — ~/.gemini/accounts/ (pool: one .json per account)
|
|
22
|
-
* active_ptr — ~/.gemini/accounts/.
|
|
22
|
+
* active_ptr — ~/.gemini/accounts/.nf-active (sidecar: name of active account)
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
const fs = require('fs');
|
|
@@ -27,7 +27,7 @@ const path = require('path');
|
|
|
27
27
|
const os = require('os');
|
|
28
28
|
const { spawn } = require('child_process');
|
|
29
29
|
|
|
30
|
-
// ─── FSM states and events (mirrors
|
|
30
|
+
// ─── FSM states and events (mirrors NFAccountManager.tla) ──────────────────
|
|
31
31
|
|
|
32
32
|
const S = Object.freeze({
|
|
33
33
|
IDLE: 'IDLE',
|
|
@@ -53,7 +53,7 @@ const E = Object.freeze({
|
|
|
53
53
|
RESET: 'RESET',
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
// Transition table derived from
|
|
56
|
+
// Transition table derived from NFAccountManager.tla Next relation.
|
|
57
57
|
// TRANSITIONS[currentState][event] = nextState
|
|
58
58
|
const TRANSITIONS = {
|
|
59
59
|
[S.IDLE]: {
|
|
@@ -115,7 +115,7 @@ function expandHome(p) {
|
|
|
115
115
|
function findProviders() {
|
|
116
116
|
const search = [
|
|
117
117
|
path.join(__dirname, 'providers.json'),
|
|
118
|
-
path.join(os.homedir(), '.claude', '
|
|
118
|
+
path.join(os.homedir(), '.claude', 'nf-bin', 'providers.json'),
|
|
119
119
|
];
|
|
120
120
|
try {
|
|
121
121
|
const cfg = JSON.parse(fs.readFileSync(path.join(os.homedir(), '.claude.json'), 'utf8'));
|
|
@@ -184,7 +184,7 @@ function getActiveFile(provider) {
|
|
|
184
184
|
// Sidecar pointer file — stores just the account name of the current active.
|
|
185
185
|
// Avoids content-diffing which breaks when tokens are silently refreshed.
|
|
186
186
|
function getActivePtr(credsDir) {
|
|
187
|
-
return path.join(credsDir, '.
|
|
187
|
+
return path.join(credsDir, '.nf-active');
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
function listPool(credsDir) {
|
|
@@ -402,7 +402,7 @@ const C = {
|
|
|
402
402
|
|
|
403
403
|
|
|
404
404
|
function printAccountsHeader(provider, pool, active) {
|
|
405
|
-
const tag = `
|
|
405
|
+
const tag = `nForma · Accounts · ${provider.name} · ${provider.display_provider ?? ''}`;
|
|
406
406
|
const border = '─'.repeat(tag.length + 4);
|
|
407
407
|
console.log('');
|
|
408
408
|
console.log(` ${C.cyan}╭${border}╮${C.reset}`);
|
|
@@ -657,11 +657,11 @@ function usage(prefix = 'node bin/account-manager.cjs') {
|
|
|
657
657
|
' --login Spawn auth login inline; auto-detect email from id_token',
|
|
658
658
|
' --name <email> Account name/email (overrides auto-detection)',
|
|
659
659
|
'',
|
|
660
|
-
'Formal spec: .planning/formal/tla/
|
|
660
|
+
'Formal spec: .planning/formal/tla/NFAccountManager.tla',
|
|
661
661
|
].join('\n'));
|
|
662
662
|
}
|
|
663
663
|
|
|
664
|
-
// ─── Exported entry point (called by
|
|
664
|
+
// ─── Exported entry point (called by nf.cjs) ─────────────────────────
|
|
665
665
|
|
|
666
666
|
async function run(argv, usagePrefix) {
|
|
667
667
|
const getArg = (f) => { const i = argv.indexOf(f); return i !== -1 && argv[i + 1] ? argv[i + 1] : null; };
|
|
@@ -68,7 +68,7 @@ function parseTraceability(content) {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
// Extract milestone from document title
|
|
71
|
-
// Matches: # Requirements:
|
|
71
|
+
// Matches: # Requirements: nForma vX.XX ... or similar
|
|
72
72
|
function extractMilestone(content) {
|
|
73
73
|
const match = content.match(/#\s+Requirements:.*?(v[\d.]+)/);
|
|
74
74
|
return match ? match[1] : 'unknown';
|
|
@@ -511,13 +511,13 @@ function generateGapReport(crossRefResults) {
|
|
|
511
511
|
const partial = crossRefResults.filter(a => a.coverage === 'partial').length;
|
|
512
512
|
const uncovered = crossRefResults.filter(a => a.coverage === 'uncovered').length;
|
|
513
513
|
|
|
514
|
-
// Generate metric names with
|
|
514
|
+
// Generate metric names with nf_ prefix and collision detection
|
|
515
515
|
const metricNameCounts = new Map();
|
|
516
516
|
const gaps = crossRefResults
|
|
517
517
|
.filter(a => a.coverage !== 'covered')
|
|
518
518
|
.map(a => {
|
|
519
|
-
// Generate canonical metric name:
|
|
520
|
-
const baseName = '
|
|
519
|
+
// Generate canonical metric name: nf_ + lowercase + replace non-alnum with _
|
|
520
|
+
const baseName = 'nf_' + a.name.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_|_$/g, '');
|
|
521
521
|
// Track for collision detection
|
|
522
522
|
const count = metricNameCounts.get(baseName) || 0;
|
|
523
523
|
metricNameCounts.set(baseName, count + 1);
|
|
@@ -59,23 +59,23 @@ const MERGE_BUDGET = {
|
|
|
59
59
|
|
|
60
60
|
// ── CFG → TLA module mapping ────────────────────────────────────────────────
|
|
61
61
|
// Reverse map: cfg base name (without .cfg) → TLA module name
|
|
62
|
-
// Needed because cfg naming is inconsistent (MCbreaker vs
|
|
62
|
+
// Needed because cfg naming is inconsistent (MCbreaker vs MCNFQuorum etc.)
|
|
63
63
|
|
|
64
64
|
const CFG_TO_MODULE = {
|
|
65
|
-
'MCbreaker': '
|
|
66
|
-
'MCoscillation': '
|
|
67
|
-
'MCconvergence': '
|
|
68
|
-
'MCdeliberation': '
|
|
69
|
-
'MCprefilter': '
|
|
70
|
-
'MCsafety': '
|
|
71
|
-
'MCliveness': '
|
|
72
|
-
'
|
|
73
|
-
'MCaccount-manager': '
|
|
74
|
-
'MCMCPEnv': '
|
|
65
|
+
'MCbreaker': 'NFCircuitBreaker',
|
|
66
|
+
'MCoscillation': 'NFOscillation',
|
|
67
|
+
'MCconvergence': 'NFConvergence',
|
|
68
|
+
'MCdeliberation': 'NFDeliberation',
|
|
69
|
+
'MCprefilter': 'NFPreFilter',
|
|
70
|
+
'MCsafety': 'NFQuorum',
|
|
71
|
+
'MCliveness': 'NFQuorum',
|
|
72
|
+
'MCNFQuorum': 'NFQuorum_xstate',
|
|
73
|
+
'MCaccount-manager': 'NFAccountManager',
|
|
74
|
+
'MCMCPEnv': 'NFMCPEnv',
|
|
75
75
|
'MCTUINavigation': 'TUINavigation',
|
|
76
|
-
'MCStopHook': '
|
|
77
|
-
'MCrecruiting-safety': '
|
|
78
|
-
'MCrecruiting-liveness': '
|
|
76
|
+
'MCStopHook': 'NFStopHook',
|
|
77
|
+
'MCrecruiting-safety': 'NFRecruiting',
|
|
78
|
+
'MCrecruiting-liveness': 'NFRecruiting',
|
|
79
79
|
};
|
|
80
80
|
|
|
81
81
|
// ── Parsing Utilities ───────────────────────────────────────────────────────
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* assumption-register.cjs — Parses assumption-gaps.md into a structured JSON
|
|
6
|
+
* register with validation status and linked L2 states.
|
|
7
|
+
*
|
|
8
|
+
* Requirements: SEM-03
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node bin/assumption-register.cjs # print summary to stdout
|
|
12
|
+
* node bin/assumption-register.cjs --json # print full register JSON to stdout
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const ROOT = process.env.PROJECT_ROOT || path.join(__dirname, '..');
|
|
19
|
+
const FORMAL = path.join(ROOT, '.planning', 'formal');
|
|
20
|
+
const GAPS_PATH = path.join(FORMAL, 'assumption-gaps.md');
|
|
21
|
+
const OUT_DIR = path.join(FORMAL, 'semantics');
|
|
22
|
+
const OUT_FILE = path.join(OUT_DIR, 'assumption-register.json');
|
|
23
|
+
|
|
24
|
+
const JSON_FLAG = process.argv.includes('--json');
|
|
25
|
+
|
|
26
|
+
// ── Parser ──────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parse a markdown table string into structured assumption entries.
|
|
30
|
+
* @param {string} content - Raw markdown content containing a pipe-delimited table
|
|
31
|
+
* @returns {Array} Parsed assumption entries
|
|
32
|
+
*/
|
|
33
|
+
function parseMarkdownTable(content) {
|
|
34
|
+
const lines = content.split('\n');
|
|
35
|
+
const assumptions = [];
|
|
36
|
+
let skipped = 0;
|
|
37
|
+
|
|
38
|
+
for (const line of lines) {
|
|
39
|
+
// Skip non-table lines, header rows, separator rows
|
|
40
|
+
if (!line.startsWith('|')) continue;
|
|
41
|
+
if (line.includes('---')) continue;
|
|
42
|
+
|
|
43
|
+
const cells = line.split('|').map(c => c.trim()).filter(c => c !== '');
|
|
44
|
+
if (cells.length < 7) {
|
|
45
|
+
// Check if this is the header row
|
|
46
|
+
if (cells[0] === '#' || cells[0] === 'Metric') continue;
|
|
47
|
+
skipped++;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const numCell = cells[0];
|
|
52
|
+
// Skip header row
|
|
53
|
+
if (numCell === '#') continue;
|
|
54
|
+
|
|
55
|
+
const id = parseInt(numCell, 10);
|
|
56
|
+
if (isNaN(id)) {
|
|
57
|
+
skipped++;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
assumptions.push({
|
|
62
|
+
id,
|
|
63
|
+
source: cells[1],
|
|
64
|
+
name: cells[2],
|
|
65
|
+
type: cells[3],
|
|
66
|
+
coverage: cells[4],
|
|
67
|
+
proposed_metric: cells[5].replace(/^`|`$/g, ''),
|
|
68
|
+
metric_type: cells[6],
|
|
69
|
+
validation_status: 'untested',
|
|
70
|
+
linked_l2_states: []
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (skipped > 0) {
|
|
75
|
+
process.stderr.write(`Warning: skipped ${skipped} malformed table lines\n`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return assumptions;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Parse assumption-gaps.md file into structured entries.
|
|
83
|
+
* @param {string} gapsPath - Path to the assumption-gaps.md file
|
|
84
|
+
* @returns {Array} Parsed assumption entries
|
|
85
|
+
*/
|
|
86
|
+
function parseAssumptionGaps(gapsPath) {
|
|
87
|
+
const content = fs.readFileSync(gapsPath, 'utf8');
|
|
88
|
+
return parseMarkdownTable(content);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ── Main ────────────────────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
function main() {
|
|
94
|
+
if (!fs.existsSync(GAPS_PATH)) {
|
|
95
|
+
console.error(`ERROR: assumption-gaps.md not found at ${GAPS_PATH}`);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const assumptions = parseAssumptionGaps(GAPS_PATH);
|
|
100
|
+
|
|
101
|
+
if (assumptions.length < 500) {
|
|
102
|
+
console.error(`ERROR: Only parsed ${assumptions.length} assumptions, expected >= 500`);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Build summary
|
|
107
|
+
const byType = {};
|
|
108
|
+
const byCoverage = {};
|
|
109
|
+
for (const a of assumptions) {
|
|
110
|
+
byType[a.type] = (byType[a.type] || 0) + 1;
|
|
111
|
+
byCoverage[a.coverage] = (byCoverage[a.coverage] || 0) + 1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const register = {
|
|
115
|
+
schema_version: '1',
|
|
116
|
+
generated: new Date().toISOString(),
|
|
117
|
+
assumptions,
|
|
118
|
+
summary: {
|
|
119
|
+
total_parsed: assumptions.length,
|
|
120
|
+
by_type: byType,
|
|
121
|
+
by_coverage: byCoverage,
|
|
122
|
+
by_validation_status: {
|
|
123
|
+
untested: assumptions.length,
|
|
124
|
+
validated: 0,
|
|
125
|
+
invalidated: 0
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
if (!fs.existsSync(OUT_DIR)) fs.mkdirSync(OUT_DIR, { recursive: true });
|
|
131
|
+
fs.writeFileSync(OUT_FILE, JSON.stringify(register, null, 2) + '\n');
|
|
132
|
+
|
|
133
|
+
if (JSON_FLAG) {
|
|
134
|
+
process.stdout.write(JSON.stringify(register, null, 2) + '\n');
|
|
135
|
+
} else {
|
|
136
|
+
console.log(`Assumption Register written to ${path.relative(ROOT, OUT_FILE)}`);
|
|
137
|
+
console.log(` Total parsed: ${assumptions.length}`);
|
|
138
|
+
console.log(` By type: ${Object.entries(byType).map(([k,v]) => `${k}=${v}`).join(' ')}`);
|
|
139
|
+
console.log(` By coverage: ${Object.entries(byCoverage).map(([k,v]) => `${k}=${v}`).join(' ')}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Export for testing
|
|
144
|
+
module.exports = { parseAssumptionGaps, parseMarkdownTable };
|
|
145
|
+
|
|
146
|
+
if (require.main === module) main();
|
|
@@ -70,7 +70,7 @@ function classifyDivergence(ttrace) {
|
|
|
70
70
|
|
|
71
71
|
const recommendation = implBugConfidence >= specBugConfidence
|
|
72
72
|
? `impl-bug: check hook implementation — context field initialization or event payload mapping`
|
|
73
|
-
: `spec-bug: review XState guard "${failingGuard || 'unknown'}" in
|
|
73
|
+
: `spec-bug: review XState guard "${failingGuard || 'unknown'}" in nf-workflow.machine.ts`;
|
|
74
74
|
|
|
75
75
|
return { specBugConfidence, implBugConfidence, failingGuard, recommendation, evidence };
|
|
76
76
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Accounts live in the macOS/system keychain managed by gh.
|
|
7
7
|
* There is no credential file to copy — all operations delegate to gh.
|
|
8
8
|
* This is the single source of truth for gh auth status parsing,
|
|
9
|
-
* replacing duplicated regex in
|
|
9
|
+
* replacing duplicated regex in nf.cjs and gh-account-rotate.cjs.
|
|
10
10
|
*
|
|
11
11
|
* Applies to: copilot-1
|
|
12
12
|
*
|
|
@@ -19,7 +19,7 @@ const acm = require('../account-manager.cjs');
|
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* list(provider) → [{ name, active }]
|
|
22
|
-
* Reads the pool directory and the .
|
|
22
|
+
* Reads the pool directory and the .nf-active pointer — no subprocess.
|
|
23
23
|
*/
|
|
24
24
|
function list(provider) {
|
|
25
25
|
const credsDir = acm.getCredsDir(provider);
|
package/bin/autoClosePtoF.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* P->F auto-close remediation dispatch
|
|
3
|
-
* Dispatches parameter updates via /
|
|
3
|
+
* Dispatches parameter updates via /nf:quick for drift entries,
|
|
4
4
|
* flags investigation for issue/invariant entries.
|
|
5
5
|
*
|
|
6
6
|
* Requirements: PF-04, PF-05
|
|
@@ -63,11 +63,11 @@ function autoClosePtoF(residual, options = {}) {
|
|
|
63
63
|
const isParameter = entry.issue_type === 'drift' && isNumericFn(entry.formal_ref, { specDir });
|
|
64
64
|
|
|
65
65
|
if (isParameter) {
|
|
66
|
-
// Parameter update track: dispatch /
|
|
66
|
+
// Parameter update track: dispatch /nf:quick
|
|
67
67
|
let dispatchOk = false;
|
|
68
68
|
|
|
69
69
|
if (spawnFn) {
|
|
70
|
-
const result = spawnFn('
|
|
70
|
+
const result = spawnFn('nf-quick.cjs', [
|
|
71
71
|
`Update formal parameter ${entry.formal_ref} to match production`,
|
|
72
72
|
`Production measurement: ${entry.meta?.measured_value}`,
|
|
73
73
|
`Current formal: ${div.expected}`,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// bin/budget-tracker.cjs
|
|
3
|
+
// Token budget calculation, profile downgrade logic, and subscription slot exclusion.
|
|
4
|
+
// Uses only node:fs, node:path. No external dependencies.
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
// Profile downgrade sequence: quality -> balanced -> budget -> null (minimum)
|
|
10
|
+
const DOWNGRADE_CHAIN = { quality: 'balanced', balanced: 'budget', budget: null };
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Compute budget status from context window usage percentage.
|
|
14
|
+
* @param {number} usedPct - Context window used percentage (0-100).
|
|
15
|
+
* @param {object} budgetConfig - budget config section from nf.json.
|
|
16
|
+
* @param {object} agentConfig - agent_config section from nf.json (unused for now, reserved for sub exclusion).
|
|
17
|
+
* @returns {object} Budget status object.
|
|
18
|
+
*/
|
|
19
|
+
function computeBudgetStatus(usedPct, budgetConfig, agentConfig) {
|
|
20
|
+
if (!budgetConfig || budgetConfig.session_limit_tokens == null) {
|
|
21
|
+
return { active: false };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const estimatedTokens = Math.round((usedPct / 100) * 200000);
|
|
25
|
+
const budgetUsedPct = Math.round((estimatedTokens / budgetConfig.session_limit_tokens) * 100);
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
active: true,
|
|
29
|
+
estimatedTokens,
|
|
30
|
+
budgetUsedPct,
|
|
31
|
+
shouldWarn: budgetUsedPct >= (budgetConfig.warn_pct || 60),
|
|
32
|
+
shouldDowngrade: budgetUsedPct >= (budgetConfig.downgrade_pct || 85),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Trigger a model profile downgrade by writing to .planning/config.json.
|
|
38
|
+
* @param {string} cwd - Project working directory.
|
|
39
|
+
* @returns {object} Downgrade result.
|
|
40
|
+
*/
|
|
41
|
+
function triggerProfileDowngrade(cwd) {
|
|
42
|
+
try {
|
|
43
|
+
const configPath = path.join(cwd, '.planning', 'config.json');
|
|
44
|
+
let config = {};
|
|
45
|
+
if (fs.existsSync(configPath)) {
|
|
46
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
47
|
+
}
|
|
48
|
+
const current = config.model_profile || 'balanced';
|
|
49
|
+
const next = DOWNGRADE_CHAIN[current];
|
|
50
|
+
if (next === undefined || next === null) {
|
|
51
|
+
return { downgraded: false, current };
|
|
52
|
+
}
|
|
53
|
+
config.model_profile = next;
|
|
54
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
55
|
+
return { downgraded: true, from: current, to: next };
|
|
56
|
+
} catch (e) {
|
|
57
|
+
return { downgraded: false, error: e.message };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Format a budget warning message for additionalContext injection.
|
|
63
|
+
* @param {object} status - Output from computeBudgetStatus.
|
|
64
|
+
* @param {object|null} downgradeResult - Output from triggerProfileDowngrade (or null).
|
|
65
|
+
* @returns {string} Warning message.
|
|
66
|
+
*/
|
|
67
|
+
function formatBudgetWarning(status, downgradeResult) {
|
|
68
|
+
if (status.shouldDowngrade && downgradeResult && downgradeResult.downgraded) {
|
|
69
|
+
return `BUDGET ALERT: Context at ${status.budgetUsedPct}% of token budget. Model profile downgraded from '${downgradeResult.from}' to '${downgradeResult.to}'. Consider running /compact.`;
|
|
70
|
+
}
|
|
71
|
+
if (status.shouldWarn) {
|
|
72
|
+
return `BUDGET WARNING: Context at ${status.budgetUsedPct}% of token budget (${status.estimatedTokens} estimated tokens of ${status.active ? (status.budgetUsedPct > 0 ? Math.round(status.estimatedTokens * 100 / status.budgetUsedPct) : 0) : 0} limit). Monitor usage and consider /compact at next clean boundary.`;
|
|
73
|
+
}
|
|
74
|
+
return '';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { DOWNGRADE_CHAIN, computeBudgetStatus, triggerProfileDowngrade, formatBudgetWarning };
|