@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
package/bin/install.js
CHANGED
|
@@ -135,23 +135,23 @@ function getGlobalDir(runtime, explicitDir = null) {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
const banner = '\n' +
|
|
138
|
-
salmon + '
|
|
139
|
-
salmon + '
|
|
140
|
-
salmon + '
|
|
141
|
-
salmon + '
|
|
142
|
-
salmon + ' ██║
|
|
143
|
-
salmon + ' ╚═╝
|
|
138
|
+
salmon + ' ' + cyan + '███████╗\n' +
|
|
139
|
+
salmon + ' ' + cyan + '██╔════╝\n' +
|
|
140
|
+
salmon + ' ██████╗ ' + cyan + '█████╗\n' +
|
|
141
|
+
salmon + ' ██╔══██╗ ' + cyan + '██╔══╝\n' +
|
|
142
|
+
salmon + ' ██║ ██║ ' + cyan + '██║\n' +
|
|
143
|
+
salmon + ' ╚═╝ ╚═╝ ' + cyan + '╚═╝' + reset + '\n' +
|
|
144
144
|
'\n' +
|
|
145
|
-
' nForma —
|
|
145
|
+
' nForma — Consensus before code. Proof before production. ' + dim + 'v' + pkg.version + reset + '\n' +
|
|
146
146
|
' Built on GSD-CC by TÂCHES.\n' +
|
|
147
|
-
'
|
|
147
|
+
' A quorum of diverse coding agents + formal verification. By Jonathan Borduas.\n' +
|
|
148
148
|
'\n' +
|
|
149
149
|
cyan + ' The task of leadership is to create an alignment of strengths\n' +
|
|
150
150
|
' so strong that it makes the system\u2019s weaknesses irrelevant.\n' +
|
|
151
151
|
dim + ' \u2014 Peter Drucker' + reset + '\n';
|
|
152
152
|
|
|
153
153
|
// nForma: MCP auto-detection — keyword map for quorum model server matching
|
|
154
|
-
const
|
|
154
|
+
const NF_KEYWORD_MAP = {
|
|
155
155
|
codex: { keywords: ['codex'], defaultPrefix: 'mcp__codex-cli-1__' },
|
|
156
156
|
gemini: { keywords: ['gemini'], defaultPrefix: 'mcp__gemini-cli-1__' },
|
|
157
157
|
opencode: { keywords: ['opencode'], defaultPrefix: 'mcp__opencode-1__' },
|
|
@@ -177,7 +177,7 @@ function buildRequiredModelsFromMcp() {
|
|
|
177
177
|
const requiredModels = {};
|
|
178
178
|
let anyDetected = false;
|
|
179
179
|
|
|
180
|
-
for (const [modelKey, { keywords, defaultPrefix }] of Object.entries(
|
|
180
|
+
for (const [modelKey, { keywords, defaultPrefix }] of Object.entries(NF_KEYWORD_MAP)) {
|
|
181
181
|
const matched = Object.keys(mcpServers).find(serverName =>
|
|
182
182
|
keywords.some(kw => serverName.toLowerCase().includes(kw))
|
|
183
183
|
);
|
|
@@ -192,7 +192,7 @@ function buildRequiredModelsFromMcp() {
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
if (!anyDetected) {
|
|
195
|
-
console.warn(` ${yellow}⚠${reset} No quorum MCP servers detected — using hardcoded defaults. Edit ~/.claude/
|
|
195
|
+
console.warn(` ${yellow}⚠${reset} No quorum MCP servers detected — using hardcoded defaults. Edit ~/.claude/nf.json to configure.`);
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
return requiredModels;
|
|
@@ -255,7 +255,7 @@ function warnMissingMcpServers() {
|
|
|
255
255
|
return;
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
for (const [modelKey, { keywords }] of Object.entries(
|
|
258
|
+
for (const [modelKey, { keywords }] of Object.entries(NF_KEYWORD_MAP)) {
|
|
259
259
|
const found = Object.keys(mcpServers).some(serverName =>
|
|
260
260
|
keywords.some(kw => serverName.toLowerCase().includes(kw))
|
|
261
261
|
);
|
|
@@ -269,7 +269,7 @@ function warnMissingMcpServers() {
|
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
// INST-01: Detects whether any claude-mcp-server quorum agents are configured.
|
|
272
|
-
// Used in finishInstall() to nudge new users to run /
|
|
272
|
+
// Used in finishInstall() to nudge new users to run /nf:mcp-setup.
|
|
273
273
|
// Fail-open: returns false on read errors (never blocks install).
|
|
274
274
|
function hasClaudeMcpAgents() {
|
|
275
275
|
const claudeJsonPath = path.join(os.homedir(), '.claude.json');
|
|
@@ -624,8 +624,8 @@ function convertClaudeToOpencodeFrontmatter(content) {
|
|
|
624
624
|
convertedContent = convertedContent.replace(/\bAskUserQuestion\b/g, 'question');
|
|
625
625
|
convertedContent = convertedContent.replace(/\bSlashCommand\b/g, 'skill');
|
|
626
626
|
convertedContent = convertedContent.replace(/\bTodoWrite\b/g, 'todowrite');
|
|
627
|
-
// Replace /
|
|
628
|
-
convertedContent = convertedContent.replace(/\/
|
|
627
|
+
// Replace /nf:command with /nf-command for opencode (flat command structure)
|
|
628
|
+
convertedContent = convertedContent.replace(/\/nf:/g, '/nf-');
|
|
629
629
|
// Replace ~/.claude with ~/.config/opencode (OpenCode's correct config location)
|
|
630
630
|
convertedContent = convertedContent.replace(/~\/\.claude\b/g, '~/.config/opencode');
|
|
631
631
|
// Replace general-purpose subagent type with OpenCode's equivalent "general"
|
|
@@ -767,12 +767,12 @@ function convertClaudeToGeminiToml(content) {
|
|
|
767
767
|
|
|
768
768
|
/**
|
|
769
769
|
* Copy commands to a flat structure for OpenCode
|
|
770
|
-
* OpenCode expects: command/
|
|
771
|
-
* Source structure: commands/
|
|
770
|
+
* OpenCode expects: command/nf-help.md (invoked as /nf-help)
|
|
771
|
+
* Source structure: commands/nf/help.md
|
|
772
772
|
*
|
|
773
|
-
* @param {string} srcDir - Source directory (e.g., commands/
|
|
773
|
+
* @param {string} srcDir - Source directory (e.g., commands/nf/)
|
|
774
774
|
* @param {string} destDir - Destination directory (e.g., command/)
|
|
775
|
-
* @param {string} prefix - Prefix for filenames (e.g., '
|
|
775
|
+
* @param {string} prefix - Prefix for filenames (e.g., 'nf')
|
|
776
776
|
* @param {string} pathPrefix - Path prefix for file references
|
|
777
777
|
* @param {string} runtime - Target runtime ('claude' or 'opencode')
|
|
778
778
|
*/
|
|
@@ -781,7 +781,7 @@ function copyFlattenedCommands(srcDir, destDir, prefix, pathPrefix, runtime) {
|
|
|
781
781
|
return;
|
|
782
782
|
}
|
|
783
783
|
|
|
784
|
-
// Remove old
|
|
784
|
+
// Remove old nf-*.md files before copying new ones
|
|
785
785
|
if (fs.existsSync(destDir)) {
|
|
786
786
|
for (const file of fs.readdirSync(destDir)) {
|
|
787
787
|
if (file.startsWith(`${prefix}-`) && file.endsWith('.md')) {
|
|
@@ -799,10 +799,10 @@ function copyFlattenedCommands(srcDir, destDir, prefix, pathPrefix, runtime) {
|
|
|
799
799
|
|
|
800
800
|
if (entry.isDirectory()) {
|
|
801
801
|
// Recurse into subdirectories, adding to prefix
|
|
802
|
-
// e.g., commands/
|
|
802
|
+
// e.g., commands/nf/debug/start.md -> command/nf-debug-start.md
|
|
803
803
|
copyFlattenedCommands(srcPath, destDir, `${prefix}-${entry.name}`, pathPrefix, runtime);
|
|
804
804
|
} else if (entry.name.endsWith('.md')) {
|
|
805
|
-
// Flatten: help.md ->
|
|
805
|
+
// Flatten: help.md -> nf-help.md
|
|
806
806
|
const baseName = entry.name.replace('.md', '');
|
|
807
807
|
const destName = `${prefix}-${baseName}.md`;
|
|
808
808
|
const destPath = path.join(destDir, destName);
|
|
@@ -884,8 +884,14 @@ function cleanupOrphanedFiles(configDir) {
|
|
|
884
884
|
const orphanedFiles = [
|
|
885
885
|
'hooks/gsd-notify.sh', // Removed in v1.6.x
|
|
886
886
|
'hooks/statusline.js', // Renamed to gsd-statusline.js in v1.9.0
|
|
887
|
-
'hooks/gsd-statusline.js', // Renamed to
|
|
888
|
-
'hooks/gsd-check-update.js', // Renamed to
|
|
887
|
+
'hooks/gsd-statusline.js', // Renamed to nf-statusline.js in v0.2
|
|
888
|
+
'hooks/gsd-check-update.js', // Renamed to nf-check-update.js in v0.2
|
|
889
|
+
'hooks/qgsd-prompt.js', // Renamed to nf-prompt.js in v0.2
|
|
890
|
+
'hooks/qgsd-stop.js', // Renamed to nf-stop.js in v0.2
|
|
891
|
+
'hooks/qgsd-circuit-breaker.js', // Renamed to nf-circuit-breaker.js in v0.2
|
|
892
|
+
'hooks/qgsd-session-start.js', // Renamed to nf-session-start.js in v0.2
|
|
893
|
+
'hooks/qgsd-check-update.js', // Renamed to nf-check-update.js in v0.2
|
|
894
|
+
'hooks/qgsd-statusline.js', // Renamed to nf-statusline.js in v0.2
|
|
889
895
|
];
|
|
890
896
|
|
|
891
897
|
for (const relPath of orphanedFiles) {
|
|
@@ -907,8 +913,14 @@ function cleanupOrphanedHooks(settings) {
|
|
|
907
913
|
'gsd-intel-index.js', // Removed in v1.9.2
|
|
908
914
|
'gsd-intel-session.js', // Removed in v1.9.2
|
|
909
915
|
'gsd-intel-prune.js', // Removed in v1.9.2
|
|
910
|
-
'hooks/gsd-check-update.js', // Renamed to
|
|
911
|
-
'hooks/gsd-statusline.js', // Renamed to
|
|
916
|
+
'hooks/gsd-check-update.js', // Renamed to nf-check-update.js in v0.2
|
|
917
|
+
'hooks/gsd-statusline.js', // Renamed to nf-statusline.js in v0.2
|
|
918
|
+
'qgsd-prompt.js', // Renamed to nf-prompt.js in v0.2
|
|
919
|
+
'qgsd-stop.js', // Renamed to nf-stop.js in v0.2
|
|
920
|
+
'qgsd-circuit-breaker.js', // Renamed to nf-circuit-breaker.js in v0.2
|
|
921
|
+
'qgsd-session-start.js', // Renamed to nf-session-start.js in v0.2
|
|
922
|
+
'qgsd-check-update.js', // Renamed to nf-check-update.js in v0.2
|
|
923
|
+
'qgsd-statusline.js', // Renamed to nf-statusline.js in v0.2
|
|
912
924
|
];
|
|
913
925
|
|
|
914
926
|
let cleanedHooks = false;
|
|
@@ -941,15 +953,16 @@ function cleanupOrphanedHooks(settings) {
|
|
|
941
953
|
console.log(` ${green}✓${reset} Removed orphaned hook registrations`);
|
|
942
954
|
}
|
|
943
955
|
|
|
944
|
-
// Fix #330 +
|
|
956
|
+
// Fix #330 + nf migration: update statusLine if it points to old statusline path
|
|
945
957
|
if (settings.statusLine && settings.statusLine.command) {
|
|
946
958
|
const cmd = settings.statusLine.command;
|
|
947
|
-
if ((cmd.includes('statusline.js') || cmd.includes('gsd-statusline.js')) &&
|
|
948
|
-
!cmd.includes('
|
|
959
|
+
if ((cmd.includes('statusline.js') || cmd.includes('gsd-statusline.js') || cmd.includes('qgsd-statusline.js')) &&
|
|
960
|
+
!cmd.includes('nf-statusline.js')) {
|
|
949
961
|
settings.statusLine.command = cmd
|
|
950
|
-
.replace(/\
|
|
951
|
-
.replace(/\
|
|
952
|
-
|
|
962
|
+
.replace(/\bqgsd-statusline\.js\b/, 'nf-statusline.js')
|
|
963
|
+
.replace(/\bgsd-statusline\.js\b/, 'nf-statusline.js')
|
|
964
|
+
.replace(/\bstatusline\.js\b/, 'nf-statusline.js');
|
|
965
|
+
console.log(` ${green}✓${reset} Updated statusline path → nf-statusline.js`);
|
|
953
966
|
}
|
|
954
967
|
}
|
|
955
968
|
|
|
@@ -992,12 +1005,12 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
992
1005
|
|
|
993
1006
|
// 1. Remove GSD commands directory
|
|
994
1007
|
if (isOpencode) {
|
|
995
|
-
// OpenCode: remove command/
|
|
1008
|
+
// OpenCode: remove command/nf-*.md files (and legacy qgsd-*.md)
|
|
996
1009
|
const commandDir = path.join(targetDir, 'command');
|
|
997
1010
|
if (fs.existsSync(commandDir)) {
|
|
998
1011
|
const files = fs.readdirSync(commandDir);
|
|
999
1012
|
for (const file of files) {
|
|
1000
|
-
if (file.startsWith('
|
|
1013
|
+
if (file.startsWith('nf-') && file.endsWith('.md')) {
|
|
1001
1014
|
fs.unlinkSync(path.join(commandDir, file));
|
|
1002
1015
|
removedCount++;
|
|
1003
1016
|
}
|
|
@@ -1005,21 +1018,21 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
1005
1018
|
console.log(` ${green}✓${reset} Removed GSD commands from command/`);
|
|
1006
1019
|
}
|
|
1007
1020
|
} else {
|
|
1008
|
-
// Claude Code & Gemini: remove commands/
|
|
1009
|
-
const gsdCommandsDir = path.join(targetDir, 'commands', '
|
|
1021
|
+
// Claude Code & Gemini: remove commands/nf/ directory
|
|
1022
|
+
const gsdCommandsDir = path.join(targetDir, 'commands', 'nf');
|
|
1010
1023
|
if (fs.existsSync(gsdCommandsDir)) {
|
|
1011
1024
|
fs.rmSync(gsdCommandsDir, { recursive: true });
|
|
1012
1025
|
removedCount++;
|
|
1013
|
-
console.log(` ${green}✓${reset} Removed commands/
|
|
1026
|
+
console.log(` ${green}✓${reset} Removed commands/nf/`);
|
|
1014
1027
|
}
|
|
1015
1028
|
}
|
|
1016
1029
|
|
|
1017
|
-
// 2. Remove
|
|
1018
|
-
const gsdDir = path.join(targetDir, '
|
|
1030
|
+
// 2. Remove nf directory
|
|
1031
|
+
const gsdDir = path.join(targetDir, 'nf');
|
|
1019
1032
|
if (fs.existsSync(gsdDir)) {
|
|
1020
1033
|
fs.rmSync(gsdDir, { recursive: true });
|
|
1021
1034
|
removedCount++;
|
|
1022
|
-
console.log(` ${green}✓${reset} Removed
|
|
1035
|
+
console.log(` ${green}✓${reset} Removed nf/`);
|
|
1023
1036
|
}
|
|
1024
1037
|
|
|
1025
1038
|
// 2b. Migration: warn about old get-shit-done/ and commands/gsd/ paths from pre-v0.2 nForma installs
|
|
@@ -1036,13 +1049,13 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
1036
1049
|
console.log();
|
|
1037
1050
|
}
|
|
1038
1051
|
|
|
1039
|
-
// 3. Remove GSD agents (
|
|
1052
|
+
// 3. Remove GSD agents (nf-*.md files only)
|
|
1040
1053
|
const agentsDir = path.join(targetDir, 'agents');
|
|
1041
1054
|
if (fs.existsSync(agentsDir)) {
|
|
1042
1055
|
const files = fs.readdirSync(agentsDir);
|
|
1043
1056
|
let agentCount = 0;
|
|
1044
1057
|
for (const file of files) {
|
|
1045
|
-
if (file.startsWith('
|
|
1058
|
+
if (file.startsWith('nf-') && file.endsWith('.md')) {
|
|
1046
1059
|
fs.unlinkSync(path.join(agentsDir, file));
|
|
1047
1060
|
agentCount++;
|
|
1048
1061
|
}
|
|
@@ -1065,7 +1078,11 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
1065
1078
|
// 4. Remove GSD hooks
|
|
1066
1079
|
const hooksDir = path.join(targetDir, 'hooks');
|
|
1067
1080
|
if (fs.existsSync(hooksDir)) {
|
|
1068
|
-
const gsdHooks = [
|
|
1081
|
+
const gsdHooks = [
|
|
1082
|
+
'nf-statusline.js', 'nf-check-update.js', 'gsd-check-update.sh',
|
|
1083
|
+
'qgsd-prompt.js', 'qgsd-stop.js', 'qgsd-circuit-breaker.js',
|
|
1084
|
+
'qgsd-session-start.js', 'qgsd-check-update.js', 'qgsd-statusline.js',
|
|
1085
|
+
];
|
|
1069
1086
|
let hookCount = 0;
|
|
1070
1087
|
for (const hook of gsdHooks) {
|
|
1071
1088
|
const hookPath = path.join(hooksDir, hook);
|
|
@@ -1102,9 +1119,9 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
1102
1119
|
let settings = readSettings(settingsPath);
|
|
1103
1120
|
let settingsModified = false;
|
|
1104
1121
|
|
|
1105
|
-
// Remove GSD statusline if it references our hook
|
|
1122
|
+
// Remove GSD statusline if it references our hook (nf-* or old qgsd-*)
|
|
1106
1123
|
if (settings.statusLine && settings.statusLine.command &&
|
|
1107
|
-
settings.statusLine.command.includes('qgsd-statusline')) {
|
|
1124
|
+
(settings.statusLine.command.includes('nf-statusline') || settings.statusLine.command.includes('qgsd-statusline'))) {
|
|
1108
1125
|
delete settings.statusLine;
|
|
1109
1126
|
settingsModified = true;
|
|
1110
1127
|
console.log(` ${green}✓${reset} Removed GSD statusline from settings`);
|
|
@@ -1118,6 +1135,9 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
1118
1135
|
// Filter out GSD hooks
|
|
1119
1136
|
const hasGsdHook = entry.hooks.some(h =>
|
|
1120
1137
|
h.command && (
|
|
1138
|
+
h.command.includes('nf-check-update') ||
|
|
1139
|
+
h.command.includes('nf-statusline') ||
|
|
1140
|
+
h.command.includes('nf-session-start') ||
|
|
1121
1141
|
h.command.includes('qgsd-check-update') ||
|
|
1122
1142
|
h.command.includes('qgsd-statusline') ||
|
|
1123
1143
|
h.command.includes('qgsd-session-start')
|
|
@@ -1140,7 +1160,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
1140
1160
|
if (settings.hooks && settings.hooks.UserPromptSubmit) {
|
|
1141
1161
|
const before = settings.hooks.UserPromptSubmit.length;
|
|
1142
1162
|
settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter(entry =>
|
|
1143
|
-
!(entry.hooks && entry.hooks.some(h => h.command && h.command.includes('qgsd-prompt')))
|
|
1163
|
+
!(entry.hooks && entry.hooks.some(h => h.command && (h.command.includes('nf-prompt') || h.command.includes('qgsd-prompt'))))
|
|
1144
1164
|
);
|
|
1145
1165
|
if (settings.hooks.UserPromptSubmit.length < before) {
|
|
1146
1166
|
settingsModified = true;
|
|
@@ -1151,7 +1171,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
1151
1171
|
if (settings.hooks && settings.hooks.Stop) {
|
|
1152
1172
|
const before = settings.hooks.Stop.length;
|
|
1153
1173
|
settings.hooks.Stop = settings.hooks.Stop.filter(entry =>
|
|
1154
|
-
!(entry.hooks && entry.hooks.some(h => h.command && h.command.includes('qgsd-stop')))
|
|
1174
|
+
!(entry.hooks && entry.hooks.some(h => h.command && (h.command.includes('nf-stop') || h.command.includes('qgsd-stop'))))
|
|
1155
1175
|
);
|
|
1156
1176
|
if (settings.hooks.Stop.length < before) {
|
|
1157
1177
|
settingsModified = true;
|
|
@@ -1162,7 +1182,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
1162
1182
|
if (settings.hooks && settings.hooks.PreToolUse) {
|
|
1163
1183
|
const before = settings.hooks.PreToolUse.length;
|
|
1164
1184
|
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(entry =>
|
|
1165
|
-
!(entry.hooks && entry.hooks.some(h => h.command && h.command.includes('qgsd-circuit-breaker')))
|
|
1185
|
+
!(entry.hooks && entry.hooks.some(h => h.command && (h.command.includes('nf-circuit-breaker') || h.command.includes('qgsd-circuit-breaker'))))
|
|
1166
1186
|
);
|
|
1167
1187
|
if (settings.hooks.PreToolUse.length < before) {
|
|
1168
1188
|
settingsModified = true;
|
|
@@ -1181,11 +1201,11 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
1181
1201
|
}
|
|
1182
1202
|
if (settings.hooks.PostToolUse.length === 0) delete settings.hooks.PostToolUse;
|
|
1183
1203
|
}
|
|
1184
|
-
// Remove
|
|
1204
|
+
// Remove nf-spec-regen hook (uninstall path)
|
|
1185
1205
|
if (settings.hooks && settings.hooks.PostToolUse) {
|
|
1186
1206
|
const before = settings.hooks.PostToolUse.length;
|
|
1187
1207
|
settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter(entry =>
|
|
1188
|
-
!(entry.hooks && entry.hooks.some(h => h.command && h.command.includes('
|
|
1208
|
+
!(entry.hooks && entry.hooks.some(h => h.command && h.command.includes('nf-spec-regen')))
|
|
1189
1209
|
);
|
|
1190
1210
|
if (settings.hooks.PostToolUse.length < before) {
|
|
1191
1211
|
settingsModified = true;
|
|
@@ -1196,7 +1216,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
1196
1216
|
if (settings.hooks && settings.hooks.PreCompact) {
|
|
1197
1217
|
const before = settings.hooks.PreCompact.length;
|
|
1198
1218
|
settings.hooks.PreCompact = settings.hooks.PreCompact.filter(entry =>
|
|
1199
|
-
!(entry.hooks && entry.hooks.some(h => h.command && h.command.includes('
|
|
1219
|
+
!(entry.hooks && entry.hooks.some(h => h.command && h.command.includes('nf-precompact')))
|
|
1200
1220
|
);
|
|
1201
1221
|
if (settings.hooks.PreCompact.length < before) {
|
|
1202
1222
|
settingsModified = true;
|
|
@@ -1235,7 +1255,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
1235
1255
|
if (config.permission[permType]) {
|
|
1236
1256
|
const keys = Object.keys(config.permission[permType]);
|
|
1237
1257
|
for (const key of keys) {
|
|
1238
|
-
if (key.includes('
|
|
1258
|
+
if (key.includes('nf')) {
|
|
1239
1259
|
delete config.permission[permType][key];
|
|
1240
1260
|
modified = true;
|
|
1241
1261
|
}
|
|
@@ -1335,7 +1355,7 @@ function parseJsonc(content) {
|
|
|
1335
1355
|
|
|
1336
1356
|
/**
|
|
1337
1357
|
* Configure OpenCode permissions to allow reading GSD reference docs
|
|
1338
|
-
* This prevents permission prompts when GSD accesses the
|
|
1358
|
+
* This prevents permission prompts when GSD accesses the nf directory
|
|
1339
1359
|
* @param {boolean} isGlobal - Whether this is a global or local install
|
|
1340
1360
|
*/
|
|
1341
1361
|
function configureOpencodePermissions(isGlobal = true) {
|
|
@@ -1373,8 +1393,8 @@ function configureOpencodePermissions(isGlobal = true) {
|
|
|
1373
1393
|
// Use ~ shorthand if it's in the default location, otherwise use full path
|
|
1374
1394
|
const defaultConfigDir = path.join(os.homedir(), '.config', 'opencode');
|
|
1375
1395
|
const gsdPath = opencodeConfigDir === defaultConfigDir
|
|
1376
|
-
? '~/.config/opencode/
|
|
1377
|
-
: `${opencodeConfigDir.replace(/\\/g, '/')}/
|
|
1396
|
+
? '~/.config/opencode/nf/*'
|
|
1397
|
+
: `${opencodeConfigDir.replace(/\\/g, '/')}/nf/*`;
|
|
1378
1398
|
|
|
1379
1399
|
let modified = false;
|
|
1380
1400
|
|
|
@@ -1448,7 +1468,7 @@ function verifyFileInstalled(filePath, description) {
|
|
|
1448
1468
|
// ──────────────────────────────────────────────────────
|
|
1449
1469
|
|
|
1450
1470
|
const PATCHES_DIR_NAME = 'gsd-local-patches';
|
|
1451
|
-
const MANIFEST_NAME = '
|
|
1471
|
+
const MANIFEST_NAME = 'nf-file-manifest.json';
|
|
1452
1472
|
|
|
1453
1473
|
/**
|
|
1454
1474
|
* Compute SHA256 hash of file contents
|
|
@@ -1482,24 +1502,24 @@ function generateManifest(dir, baseDir) {
|
|
|
1482
1502
|
* Write file manifest after installation for future modification detection
|
|
1483
1503
|
*/
|
|
1484
1504
|
function writeManifest(configDir) {
|
|
1485
|
-
const gsdDir = path.join(configDir, '
|
|
1486
|
-
const commandsDir = path.join(configDir, 'commands', '
|
|
1505
|
+
const gsdDir = path.join(configDir, 'nf');
|
|
1506
|
+
const commandsDir = path.join(configDir, 'commands', 'nf');
|
|
1487
1507
|
const agentsDir = path.join(configDir, 'agents');
|
|
1488
1508
|
const manifest = { version: pkg.version, timestamp: new Date().toISOString(), files: {} };
|
|
1489
1509
|
|
|
1490
1510
|
const gsdHashes = generateManifest(gsdDir);
|
|
1491
1511
|
for (const [rel, hash] of Object.entries(gsdHashes)) {
|
|
1492
|
-
manifest.files['
|
|
1512
|
+
manifest.files['nf/' + rel] = hash;
|
|
1493
1513
|
}
|
|
1494
1514
|
if (fs.existsSync(commandsDir)) {
|
|
1495
1515
|
const cmdHashes = generateManifest(commandsDir);
|
|
1496
1516
|
for (const [rel, hash] of Object.entries(cmdHashes)) {
|
|
1497
|
-
manifest.files['commands/
|
|
1517
|
+
manifest.files['commands/nf/' + rel] = hash;
|
|
1498
1518
|
}
|
|
1499
1519
|
}
|
|
1500
1520
|
if (fs.existsSync(agentsDir)) {
|
|
1501
1521
|
for (const file of fs.readdirSync(agentsDir)) {
|
|
1502
|
-
if (file.startsWith('
|
|
1522
|
+
if (file.startsWith('nf-') && file.endsWith('.md')) {
|
|
1503
1523
|
manifest.files['agents/' + file] = fileHash(path.join(agentsDir, file));
|
|
1504
1524
|
}
|
|
1505
1525
|
}
|
|
@@ -1569,7 +1589,7 @@ function reportLocalPatches(configDir) {
|
|
|
1569
1589
|
}
|
|
1570
1590
|
console.log('');
|
|
1571
1591
|
console.log(' Your modifications are saved in ' + cyan + PATCHES_DIR_NAME + '/' + reset);
|
|
1572
|
-
console.log(' Run ' + cyan + '/
|
|
1592
|
+
console.log(' Run ' + cyan + '/nf:reapply-patches' + reset + ' to merge them into the new version.');
|
|
1573
1593
|
console.log(' Or manually compare and merge the files.');
|
|
1574
1594
|
console.log('');
|
|
1575
1595
|
}
|
|
@@ -1620,38 +1640,38 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
1620
1640
|
const commandDir = path.join(targetDir, 'command');
|
|
1621
1641
|
fs.mkdirSync(commandDir, { recursive: true });
|
|
1622
1642
|
|
|
1623
|
-
// Copy commands/
|
|
1624
|
-
const gsdSrc = path.join(src, 'commands', '
|
|
1625
|
-
copyFlattenedCommands(gsdSrc, commandDir, '
|
|
1626
|
-
if (verifyInstalled(commandDir, 'command/
|
|
1627
|
-
const count = fs.readdirSync(commandDir).filter(f => f.startsWith('
|
|
1643
|
+
// Copy commands/nf/*.md as command/nf-*.md (flatten structure)
|
|
1644
|
+
const gsdSrc = path.join(src, 'commands', 'nf');
|
|
1645
|
+
copyFlattenedCommands(gsdSrc, commandDir, 'nf', pathPrefix, runtime);
|
|
1646
|
+
if (verifyInstalled(commandDir, 'command/nf-*')) {
|
|
1647
|
+
const count = fs.readdirSync(commandDir).filter(f => f.startsWith('nf-')).length;
|
|
1628
1648
|
console.log(` ${green}✓${reset} Installed ${count} commands to command/`);
|
|
1629
1649
|
} else {
|
|
1630
|
-
failures.push('command/
|
|
1650
|
+
failures.push('command/nf-*');
|
|
1631
1651
|
}
|
|
1632
1652
|
} else {
|
|
1633
1653
|
// Claude Code & Gemini: nested structure in commands/ directory
|
|
1634
1654
|
const commandsDir = path.join(targetDir, 'commands');
|
|
1635
1655
|
fs.mkdirSync(commandsDir, { recursive: true });
|
|
1636
1656
|
|
|
1637
|
-
const gsdSrc = path.join(src, 'commands', '
|
|
1638
|
-
const gsdDest = path.join(commandsDir, '
|
|
1657
|
+
const gsdSrc = path.join(src, 'commands', 'nf');
|
|
1658
|
+
const gsdDest = path.join(commandsDir, 'nf');
|
|
1639
1659
|
copyWithPathReplacement(gsdSrc, gsdDest, pathPrefix, runtime);
|
|
1640
|
-
if (verifyInstalled(gsdDest, 'commands/
|
|
1641
|
-
console.log(` ${green}✓${reset} Installed commands/
|
|
1660
|
+
if (verifyInstalled(gsdDest, 'commands/nf')) {
|
|
1661
|
+
console.log(` ${green}✓${reset} Installed commands/nf`);
|
|
1642
1662
|
} else {
|
|
1643
|
-
failures.push('commands/
|
|
1663
|
+
failures.push('commands/nf');
|
|
1644
1664
|
}
|
|
1645
1665
|
}
|
|
1646
1666
|
|
|
1647
|
-
// Copy
|
|
1648
|
-
const skillSrc = path.join(src, '
|
|
1649
|
-
const skillDest = path.join(targetDir, '
|
|
1667
|
+
// Copy nf skill with path replacement
|
|
1668
|
+
const skillSrc = path.join(src, 'core');
|
|
1669
|
+
const skillDest = path.join(targetDir, 'nf');
|
|
1650
1670
|
copyWithPathReplacement(skillSrc, skillDest, pathPrefix, runtime);
|
|
1651
|
-
if (verifyInstalled(skillDest, '
|
|
1652
|
-
console.log(` ${green}✓${reset} Installed
|
|
1671
|
+
if (verifyInstalled(skillDest, 'nf')) {
|
|
1672
|
+
console.log(` ${green}✓${reset} Installed nf`);
|
|
1653
1673
|
} else {
|
|
1654
|
-
failures.push('
|
|
1674
|
+
failures.push('nf');
|
|
1655
1675
|
}
|
|
1656
1676
|
|
|
1657
1677
|
// Copy agents to agents directory
|
|
@@ -1660,10 +1680,10 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
1660
1680
|
const agentsDest = path.join(targetDir, 'agents');
|
|
1661
1681
|
fs.mkdirSync(agentsDest, { recursive: true });
|
|
1662
1682
|
|
|
1663
|
-
// Remove old
|
|
1683
|
+
// Remove old nf-*.md files before copying new ones
|
|
1664
1684
|
if (fs.existsSync(agentsDest)) {
|
|
1665
1685
|
for (const file of fs.readdirSync(agentsDest)) {
|
|
1666
|
-
if (file.startsWith('
|
|
1686
|
+
if (file.startsWith('nf-') && file.endsWith('.md')) {
|
|
1667
1687
|
fs.unlinkSync(path.join(agentsDest, file));
|
|
1668
1688
|
}
|
|
1669
1689
|
}
|
|
@@ -1696,7 +1716,7 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
1696
1716
|
|
|
1697
1717
|
// Copy CHANGELOG.md
|
|
1698
1718
|
const changelogSrc = path.join(src, 'CHANGELOG.md');
|
|
1699
|
-
const changelogDest = path.join(targetDir, '
|
|
1719
|
+
const changelogDest = path.join(targetDir, 'nf', 'CHANGELOG.md');
|
|
1700
1720
|
if (fs.existsSync(changelogSrc)) {
|
|
1701
1721
|
fs.copyFileSync(changelogSrc, changelogDest);
|
|
1702
1722
|
if (verifyFileInstalled(changelogDest, 'CHANGELOG.md')) {
|
|
@@ -1707,7 +1727,7 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
1707
1727
|
}
|
|
1708
1728
|
|
|
1709
1729
|
// Write VERSION file
|
|
1710
|
-
const versionDest = path.join(targetDir, '
|
|
1730
|
+
const versionDest = path.join(targetDir, 'nf', 'VERSION');
|
|
1711
1731
|
fs.writeFileSync(versionDest, pkg.version);
|
|
1712
1732
|
if (verifyFileInstalled(versionDest, 'VERSION')) {
|
|
1713
1733
|
console.log(` ${green}✓${reset} Wrote VERSION (${pkg.version})`);
|
|
@@ -1751,10 +1771,10 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
1751
1771
|
}
|
|
1752
1772
|
}
|
|
1753
1773
|
|
|
1754
|
-
// Copy bin/*.cjs scripts to
|
|
1774
|
+
// Copy bin/*.cjs scripts to nf-bin/ (used by commands with absolute paths)
|
|
1755
1775
|
const binSrc = path.join(src, 'bin');
|
|
1756
1776
|
if (fs.existsSync(binSrc)) {
|
|
1757
|
-
const binDest = path.join(targetDir, '
|
|
1777
|
+
const binDest = path.join(targetDir, 'nf-bin');
|
|
1758
1778
|
fs.mkdirSync(binDest, { recursive: true });
|
|
1759
1779
|
const binEntries = fs.readdirSync(binSrc);
|
|
1760
1780
|
for (const entry of binEntries) {
|
|
@@ -1762,7 +1782,7 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
1762
1782
|
fs.copyFileSync(path.join(binSrc, entry), path.join(binDest, entry));
|
|
1763
1783
|
}
|
|
1764
1784
|
}
|
|
1765
|
-
console.log(` ${green}✓${reset} Installed
|
|
1785
|
+
console.log(` ${green}✓${reset} Installed nf-bin scripts`);
|
|
1766
1786
|
}
|
|
1767
1787
|
|
|
1768
1788
|
if (failures.length > 0) {
|
|
@@ -1775,11 +1795,11 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
1775
1795
|
const settingsPath = path.join(targetDir, 'settings.json');
|
|
1776
1796
|
const settings = cleanupOrphanedHooks(readSettings(settingsPath));
|
|
1777
1797
|
const statuslineCommand = isGlobal
|
|
1778
|
-
? buildHookCommand(targetDir, '
|
|
1779
|
-
: 'node ' + dirName + '/hooks/
|
|
1798
|
+
? buildHookCommand(targetDir, 'nf-statusline.js')
|
|
1799
|
+
: 'node ' + dirName + '/hooks/nf-statusline.js';
|
|
1780
1800
|
const updateCheckCommand = isGlobal
|
|
1781
|
-
? buildHookCommand(targetDir, '
|
|
1782
|
-
: 'node ' + dirName + '/hooks/
|
|
1801
|
+
? buildHookCommand(targetDir, 'nf-check-update.js')
|
|
1802
|
+
: 'node ' + dirName + '/hooks/nf-check-update.js';
|
|
1783
1803
|
|
|
1784
1804
|
// Enable experimental agents for Gemini CLI (required for custom sub-agents)
|
|
1785
1805
|
if (isGemini) {
|
|
@@ -1802,7 +1822,7 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
1802
1822
|
}
|
|
1803
1823
|
|
|
1804
1824
|
const hasGsdUpdateHook = settings.hooks.SessionStart.some(entry =>
|
|
1805
|
-
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('
|
|
1825
|
+
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('nf-check-update'))
|
|
1806
1826
|
);
|
|
1807
1827
|
|
|
1808
1828
|
if (!hasGsdUpdateHook) {
|
|
@@ -1819,14 +1839,14 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
1819
1839
|
|
|
1820
1840
|
// Register nForma session-start secret sync hook
|
|
1821
1841
|
const hasGsdSessionStartHook = settings.hooks.SessionStart.some(entry =>
|
|
1822
|
-
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('
|
|
1842
|
+
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('nf-session-start'))
|
|
1823
1843
|
);
|
|
1824
1844
|
if (!hasGsdSessionStartHook) {
|
|
1825
1845
|
settings.hooks.SessionStart.push({
|
|
1826
1846
|
hooks: [
|
|
1827
1847
|
{
|
|
1828
1848
|
type: 'command',
|
|
1829
|
-
command: buildHookCommand(targetDir, '
|
|
1849
|
+
command: buildHookCommand(targetDir, 'nf-session-start.js')
|
|
1830
1850
|
}
|
|
1831
1851
|
]
|
|
1832
1852
|
});
|
|
@@ -1836,27 +1856,52 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
1836
1856
|
// INST-05: Warn (yellow) if quorum MCP servers are absent — runs every install
|
|
1837
1857
|
warnMissingMcpServers();
|
|
1838
1858
|
|
|
1859
|
+
// ── MIGRATION: remove old qgsd-* hook entries ─────────────────────────
|
|
1860
|
+
// Old installs registered hooks as qgsd-prompt.js, qgsd-stop.js, qgsd-circuit-breaker.js.
|
|
1861
|
+
// Remove them so the nf-* replacements below can register cleanly.
|
|
1862
|
+
const OLD_HOOK_MAP = {
|
|
1863
|
+
UserPromptSubmit: 'qgsd-prompt',
|
|
1864
|
+
Stop: 'qgsd-stop',
|
|
1865
|
+
PreToolUse: 'qgsd-circuit-breaker',
|
|
1866
|
+
SessionStart: ['qgsd-check-update', 'qgsd-session-start'],
|
|
1867
|
+
};
|
|
1868
|
+
for (const [event, oldNames] of Object.entries(OLD_HOOK_MAP)) {
|
|
1869
|
+
if (settings.hooks[event]) {
|
|
1870
|
+
const names = Array.isArray(oldNames) ? oldNames : [oldNames];
|
|
1871
|
+
const before = settings.hooks[event].length;
|
|
1872
|
+
settings.hooks[event] = settings.hooks[event].filter(entry =>
|
|
1873
|
+
!(entry.hooks && entry.hooks.some(h =>
|
|
1874
|
+
h.command && names.some(n => h.command.includes(n))
|
|
1875
|
+
))
|
|
1876
|
+
);
|
|
1877
|
+
if (settings.hooks[event].length < before) {
|
|
1878
|
+
console.log(` ${green}✓${reset} Migrated old ${names.join(', ')} hook(s) → nf equivalent`);
|
|
1879
|
+
}
|
|
1880
|
+
if (settings.hooks[event].length === 0) delete settings.hooks[event];
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1839
1884
|
// Register nForma UserPromptSubmit hook (quorum injection)
|
|
1840
1885
|
// MUST be in settings.json — plugin hooks.json silently discards UserPromptSubmit output (GitHub #10225)
|
|
1841
1886
|
if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
|
|
1842
|
-
const
|
|
1843
|
-
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('
|
|
1887
|
+
const hasNfPromptHook = settings.hooks.UserPromptSubmit.some(entry =>
|
|
1888
|
+
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('nf-prompt'))
|
|
1844
1889
|
);
|
|
1845
|
-
if (!
|
|
1890
|
+
if (!hasNfPromptHook) {
|
|
1846
1891
|
settings.hooks.UserPromptSubmit.push({
|
|
1847
|
-
hooks: [{ type: 'command', command: buildHookCommand(targetDir, '
|
|
1892
|
+
hooks: [{ type: 'command', command: buildHookCommand(targetDir, 'nf-prompt.js') }]
|
|
1848
1893
|
});
|
|
1849
1894
|
console.log(` ${green}✓${reset} Configured nForma quorum injection hook (UserPromptSubmit)`);
|
|
1850
1895
|
}
|
|
1851
1896
|
|
|
1852
1897
|
// Register nForma Stop hook (quorum gate — verifies quorum evidence before Claude delivers planning output)
|
|
1853
1898
|
if (!settings.hooks.Stop) settings.hooks.Stop = [];
|
|
1854
|
-
const
|
|
1855
|
-
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('
|
|
1899
|
+
const hasNfStopHook = settings.hooks.Stop.some(entry =>
|
|
1900
|
+
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('nf-stop'))
|
|
1856
1901
|
);
|
|
1857
|
-
if (!
|
|
1902
|
+
if (!hasNfStopHook) {
|
|
1858
1903
|
settings.hooks.Stop.push({
|
|
1859
|
-
hooks: [{ type: 'command', command: buildHookCommand(targetDir, '
|
|
1904
|
+
hooks: [{ type: 'command', command: buildHookCommand(targetDir, 'nf-stop.js'), timeout: 30 }]
|
|
1860
1905
|
});
|
|
1861
1906
|
console.log(` ${green}✓${reset} Configured nForma quorum gate hook (Stop)`);
|
|
1862
1907
|
}
|
|
@@ -1864,11 +1909,11 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
1864
1909
|
// INST-08: Register nForma circuit breaker hook (PreToolUse — Claude Code only)
|
|
1865
1910
|
if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
|
|
1866
1911
|
const hasCircuitBreakerHook = settings.hooks.PreToolUse.some(entry =>
|
|
1867
|
-
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('
|
|
1912
|
+
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('nf-circuit-breaker'))
|
|
1868
1913
|
);
|
|
1869
1914
|
if (!hasCircuitBreakerHook) {
|
|
1870
1915
|
settings.hooks.PreToolUse.push({
|
|
1871
|
-
hooks: [{ type: 'command', command: buildHookCommand(targetDir, '
|
|
1916
|
+
hooks: [{ type: 'command', command: buildHookCommand(targetDir, 'nf-circuit-breaker.js'), timeout: 10 }]
|
|
1872
1917
|
});
|
|
1873
1918
|
console.log(` ${green}✓${reset} Configured nForma circuit breaker hook (PreToolUse)`);
|
|
1874
1919
|
}
|
|
@@ -1888,11 +1933,11 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
1888
1933
|
// Register nForma spec-regen hook (PostToolUse — auto-regenerate specs on machine file write)
|
|
1889
1934
|
if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
|
|
1890
1935
|
const hasSpecRegenHook = settings.hooks.PostToolUse.some(entry =>
|
|
1891
|
-
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('
|
|
1936
|
+
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('nf-spec-regen'))
|
|
1892
1937
|
);
|
|
1893
1938
|
if (!hasSpecRegenHook) {
|
|
1894
1939
|
settings.hooks.PostToolUse.push({
|
|
1895
|
-
hooks: [{ type: 'command', command: buildHookCommand(targetDir, '
|
|
1940
|
+
hooks: [{ type: 'command', command: buildHookCommand(targetDir, 'nf-spec-regen.js') }]
|
|
1896
1941
|
});
|
|
1897
1942
|
console.log(` ${green}✓${reset} Configured nForma spec-regen hook (PostToolUse)`);
|
|
1898
1943
|
}
|
|
@@ -1900,11 +1945,11 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
1900
1945
|
// Register nForma PreCompact hook (phase state injection at compaction time)
|
|
1901
1946
|
if (!settings.hooks.PreCompact) settings.hooks.PreCompact = [];
|
|
1902
1947
|
const hasPreCompactHook = settings.hooks.PreCompact.some(entry =>
|
|
1903
|
-
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('
|
|
1948
|
+
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('nf-precompact'))
|
|
1904
1949
|
);
|
|
1905
1950
|
if (!hasPreCompactHook) {
|
|
1906
1951
|
settings.hooks.PreCompact.push({
|
|
1907
|
-
hooks: [{ type: 'command', command: buildHookCommand(targetDir, '
|
|
1952
|
+
hooks: [{ type: 'command', command: buildHookCommand(targetDir, 'nf-precompact.js') }]
|
|
1908
1953
|
});
|
|
1909
1954
|
console.log(` ${green}✓${reset} Configured nForma PreCompact hook (phase state injection)`);
|
|
1910
1955
|
}
|
|
@@ -1912,12 +1957,12 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
1912
1957
|
// Register nForma token collector hook (SubagentStop — logs per-slot token usage)
|
|
1913
1958
|
if (!settings.hooks.SubagentStop) settings.hooks.SubagentStop = [];
|
|
1914
1959
|
const hasTokenCollectorHook = settings.hooks.SubagentStop.some(entry =>
|
|
1915
|
-
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('
|
|
1960
|
+
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('nf-token-collector'))
|
|
1916
1961
|
);
|
|
1917
1962
|
if (!hasTokenCollectorHook) {
|
|
1918
1963
|
settings.hooks.SubagentStop.push({
|
|
1919
|
-
matcher: '
|
|
1920
|
-
hooks: [{ type: 'command', command: buildHookCommand(targetDir, '
|
|
1964
|
+
matcher: 'nf-quorum-slot-worker',
|
|
1965
|
+
hooks: [{ type: 'command', command: buildHookCommand(targetDir, 'nf-token-collector.js'), async: true }]
|
|
1921
1966
|
});
|
|
1922
1967
|
console.log(` ${green}✓${reset} Configured nForma token collector hook (SubagentStop)`);
|
|
1923
1968
|
}
|
|
@@ -1925,29 +1970,29 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
1925
1970
|
// Register nForma slot correlator hook (SubagentStart — writes agent_id correlation file)
|
|
1926
1971
|
if (!settings.hooks.SubagentStart) settings.hooks.SubagentStart = [];
|
|
1927
1972
|
const hasSlotCorrelatorHook = settings.hooks.SubagentStart.some(entry =>
|
|
1928
|
-
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('
|
|
1973
|
+
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('nf-slot-correlator'))
|
|
1929
1974
|
);
|
|
1930
1975
|
if (!hasSlotCorrelatorHook) {
|
|
1931
1976
|
settings.hooks.SubagentStart.push({
|
|
1932
|
-
matcher: '
|
|
1933
|
-
hooks: [{ type: 'command', command: buildHookCommand(targetDir, '
|
|
1977
|
+
matcher: 'nf-quorum-slot-worker',
|
|
1978
|
+
hooks: [{ type: 'command', command: buildHookCommand(targetDir, 'nf-slot-correlator.js'), async: true }]
|
|
1934
1979
|
});
|
|
1935
1980
|
console.log(` ${green}✓${reset} Configured nForma slot correlator hook (SubagentStart)`);
|
|
1936
1981
|
}
|
|
1937
1982
|
|
|
1938
1983
|
// Write nForma config — skip if exists unless --redetect-mcps flag set
|
|
1939
|
-
const
|
|
1984
|
+
const nfConfigPath = path.join(targetDir, 'nf.json');
|
|
1940
1985
|
|
|
1941
1986
|
// --redetect-mcps: delete existing config so fresh detection runs below
|
|
1942
|
-
if (hasRedetectMcps && fs.existsSync(
|
|
1943
|
-
fs.unlinkSync(
|
|
1987
|
+
if (hasRedetectMcps && fs.existsSync(nfConfigPath)) {
|
|
1988
|
+
fs.unlinkSync(nfConfigPath);
|
|
1944
1989
|
console.log(` ${cyan}◆${reset} Re-detecting MCP prefixes (--redetect-mcps)...`);
|
|
1945
1990
|
}
|
|
1946
1991
|
|
|
1947
|
-
if (!fs.existsSync(
|
|
1992
|
+
if (!fs.existsSync(nfConfigPath)) {
|
|
1948
1993
|
// Build config with auto-detected MCP prefixes
|
|
1949
1994
|
const detectedModels = buildRequiredModelsFromMcp();
|
|
1950
|
-
const
|
|
1995
|
+
const nfConfig = {
|
|
1951
1996
|
quorum_commands: [
|
|
1952
1997
|
'plan-phase', 'new-project', 'new-milestone',
|
|
1953
1998
|
'discuss-phase', 'verify-work', 'research-phase',
|
|
@@ -1964,25 +2009,25 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
1964
2009
|
},
|
|
1965
2010
|
};
|
|
1966
2011
|
|
|
1967
|
-
fs.writeFileSync(
|
|
1968
|
-
console.log(` ${green}✓${reset} Wrote nForma config with detected MCP prefixes (~/.claude/
|
|
1969
|
-
console.log(` ${green}✓${reset} Wrote quorum_active (${
|
|
2012
|
+
fs.writeFileSync(nfConfigPath, JSON.stringify(nfConfig, null, 2) + '\n', 'utf8');
|
|
2013
|
+
console.log(` ${green}✓${reset} Wrote nForma config with detected MCP prefixes (~/.claude/nf.json)`);
|
|
2014
|
+
console.log(` ${green}✓${reset} Wrote quorum_active (${nfConfig.quorum_active.length} slots) to nf.json`);
|
|
1970
2015
|
} else {
|
|
1971
2016
|
// INST-06: print active config summary on reinstall
|
|
1972
2017
|
try {
|
|
1973
|
-
const existingConfig = JSON.parse(fs.readFileSync(
|
|
2018
|
+
const existingConfig = JSON.parse(fs.readFileSync(nfConfigPath, 'utf8'));
|
|
1974
2019
|
const models = existingConfig.required_models || {};
|
|
1975
2020
|
const summary = Object.entries(models)
|
|
1976
2021
|
.map(([key, def]) => `${key} → ${def.tool_prefix || '(unset)'}`)
|
|
1977
2022
|
.join(', ');
|
|
1978
|
-
console.log(` ${dim}↳ ~/.claude/
|
|
2023
|
+
console.log(` ${dim}↳ ~/.claude/nf.json exists — active config: ${summary}${reset}`);
|
|
1979
2024
|
console.log(` ${dim} (run with --redetect-mcps to refresh MCP prefix detection)${reset}`);
|
|
1980
2025
|
|
|
1981
2026
|
// INST-10: Add missing circuit_breaker block or missing sub-keys without touching existing user values
|
|
1982
2027
|
if (!existingConfig.circuit_breaker) {
|
|
1983
2028
|
existingConfig.circuit_breaker = { oscillation_depth: 3, commit_window: 6 };
|
|
1984
|
-
fs.writeFileSync(
|
|
1985
|
-
console.log(` ${green}✓${reset} Added circuit_breaker config block to
|
|
2029
|
+
fs.writeFileSync(nfConfigPath, JSON.stringify(existingConfig, null, 2) + '\n', 'utf8');
|
|
2030
|
+
console.log(` ${green}✓${reset} Added circuit_breaker config block to nf.json`);
|
|
1986
2031
|
} else {
|
|
1987
2032
|
// Backfill individual missing sub-keys without touching values the user has set
|
|
1988
2033
|
let subKeyAdded = false;
|
|
@@ -1995,8 +2040,8 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
1995
2040
|
subKeyAdded = true;
|
|
1996
2041
|
}
|
|
1997
2042
|
if (subKeyAdded) {
|
|
1998
|
-
fs.writeFileSync(
|
|
1999
|
-
console.log(` ${green}✓${reset} Added missing circuit_breaker sub-keys to
|
|
2043
|
+
fs.writeFileSync(nfConfigPath, JSON.stringify(existingConfig, null, 2) + '\n', 'utf8');
|
|
2044
|
+
console.log(` ${green}✓${reset} Added missing circuit_breaker sub-keys to nf.json`);
|
|
2000
2045
|
}
|
|
2001
2046
|
}
|
|
2002
2047
|
|
|
@@ -2005,8 +2050,8 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2005
2050
|
const discoveredSlots = buildActiveSlots();
|
|
2006
2051
|
if (discoveredSlots.length > 0) {
|
|
2007
2052
|
existingConfig.quorum_active = discoveredSlots;
|
|
2008
|
-
fs.writeFileSync(
|
|
2009
|
-
console.log(` ${green}✓${reset} Backfilled quorum_active (${discoveredSlots.length} slots) in
|
|
2053
|
+
fs.writeFileSync(nfConfigPath, JSON.stringify(existingConfig, null, 2) + '\n', 'utf8');
|
|
2054
|
+
console.log(` ${green}✓${reset} Backfilled quorum_active (${discoveredSlots.length} slots) in nf.json`);
|
|
2010
2055
|
}
|
|
2011
2056
|
}
|
|
2012
2057
|
|
|
@@ -2017,7 +2062,7 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2017
2062
|
const allCurrentSlots = buildActiveSlots();
|
|
2018
2063
|
const newSlots = allCurrentSlots.filter(s => !existingConfig.quorum_active.includes(s));
|
|
2019
2064
|
for (const newSlot of newSlots) {
|
|
2020
|
-
const result = addSlotToQuorumActive(newSlot,
|
|
2065
|
+
const result = addSlotToQuorumActive(newSlot, nfConfigPath);
|
|
2021
2066
|
if (result.added) {
|
|
2022
2067
|
console.log(` ${green}✓${reset} Added new slot to quorum_active: ${newSlot}`);
|
|
2023
2068
|
existingConfig.quorum_active.push(newSlot); // keep in-memory copy consistent
|
|
@@ -2026,7 +2071,7 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2026
2071
|
}
|
|
2027
2072
|
// If quorum_active is already set and non-empty: do NOT overwrite (user config preserved)
|
|
2028
2073
|
} catch {
|
|
2029
|
-
console.log(` ${dim}↳ ~/.claude/
|
|
2074
|
+
console.log(` ${dim}↳ ~/.claude/nf.json already exists — skipping (user config preserved)${reset}`);
|
|
2030
2075
|
}
|
|
2031
2076
|
}
|
|
2032
2077
|
}
|
|
@@ -2067,11 +2112,11 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
2067
2112
|
if (runtime === 'opencode') program = 'OpenCode';
|
|
2068
2113
|
if (runtime === 'gemini') program = 'Gemini';
|
|
2069
2114
|
|
|
2070
|
-
const command = isOpencode ? '/
|
|
2115
|
+
const command = isOpencode ? '/nf-help' : '/nf:help';
|
|
2071
2116
|
|
|
2072
2117
|
let nudge = '';
|
|
2073
2118
|
if (runtime === 'claude' && !hasClaudeMcpAgents()) {
|
|
2074
|
-
nudge = `\n ${yellow}⚠${reset} No quorum agents configured.\n Run ${cyan}/
|
|
2119
|
+
nudge = `\n ${yellow}⚠${reset} No quorum agents configured.\n Run ${cyan}/nf:mcp-setup${reset} in Claude Code to set up your agents.\n`;
|
|
2075
2120
|
}
|
|
2076
2121
|
|
|
2077
2122
|
console.log(`
|
|
@@ -2332,7 +2377,7 @@ if (hasDisableBreaker) {
|
|
|
2332
2377
|
: {};
|
|
2333
2378
|
fs.writeFileSync(stateFile, JSON.stringify({ ...existing, disabled: true, active: false }, null, 2), 'utf8');
|
|
2334
2379
|
console.log(` ${yellow}⊘${reset} Circuit breaker ${yellow}disabled${reset}. Detection and enforcement paused.`);
|
|
2335
|
-
console.log(` Run ${cyan}npx
|
|
2380
|
+
console.log(` Run ${cyan}npx nforma --enable-breaker${reset} to resume.`);
|
|
2336
2381
|
process.exit(0);
|
|
2337
2382
|
}
|
|
2338
2383
|
|
|
@@ -2357,14 +2402,14 @@ if (hasEnableBreaker) {
|
|
|
2357
2402
|
process.exit(0);
|
|
2358
2403
|
}
|
|
2359
2404
|
|
|
2360
|
-
// SLOT-02: --migrate-slots renames mcpServers keys and patches
|
|
2405
|
+
// SLOT-02: --migrate-slots renames mcpServers keys and patches nf.json tool_prefix values
|
|
2361
2406
|
if (hasMigrateSlots) {
|
|
2362
|
-
const { migrateClaudeJson,
|
|
2407
|
+
const { migrateClaudeJson, migrateNfJson } = require('./migrate-to-slots.cjs');
|
|
2363
2408
|
const claudeJsonPath = path.join(os.homedir(), '.claude.json');
|
|
2364
|
-
const
|
|
2409
|
+
const nfJsonPath = path.join(os.homedir(), '.claude', 'nf.json');
|
|
2365
2410
|
const dryRun = args.includes('--dry-run');
|
|
2366
2411
|
const r1 = migrateClaudeJson(claudeJsonPath, dryRun);
|
|
2367
|
-
const r2 =
|
|
2412
|
+
const r2 = migrateNfJson(nfJsonPath, dryRun);
|
|
2368
2413
|
if (r1.changed === 0 && r2.changed === 0) {
|
|
2369
2414
|
console.log(` ${green}✓${reset} Already migrated — no changes needed`);
|
|
2370
2415
|
} else {
|
|
@@ -2373,7 +2418,7 @@ if (hasMigrateSlots) {
|
|
|
2373
2418
|
r1.renamed.forEach(r => console.log(` ${r.from} → ${r.to}`));
|
|
2374
2419
|
}
|
|
2375
2420
|
if (r2.changed > 0) {
|
|
2376
|
-
console.log(` ${green}✓${reset} Patched ${r2.changed}
|
|
2421
|
+
console.log(` ${green}✓${reset} Patched ${r2.changed} nf.json tool_prefix values`);
|
|
2377
2422
|
}
|
|
2378
2423
|
}
|
|
2379
2424
|
process.exit(0);
|