@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.
Files changed (193) hide show
  1. package/README.md +2 -2
  2. package/agents/{qgsd-codebase-mapper.md → nf-codebase-mapper.md} +1 -1
  3. package/agents/{qgsd-debugger.md → nf-debugger.md} +3 -3
  4. package/agents/{qgsd-executor.md → nf-executor.md} +14 -14
  5. package/agents/{qgsd-integration-checker.md → nf-integration-checker.md} +1 -1
  6. package/agents/{qgsd-phase-researcher.md → nf-phase-researcher.md} +6 -6
  7. package/agents/{qgsd-plan-checker.md → nf-plan-checker.md} +9 -9
  8. package/agents/{qgsd-planner.md → nf-planner.md} +9 -9
  9. package/agents/{qgsd-project-researcher.md → nf-project-researcher.md} +2 -2
  10. package/agents/{qgsd-quorum-orchestrator.md → nf-quorum-orchestrator.md} +33 -33
  11. package/agents/{qgsd-quorum-slot-worker.md → nf-quorum-slot-worker.md} +3 -3
  12. package/agents/{qgsd-quorum-synthesizer.md → nf-quorum-synthesizer.md} +3 -3
  13. package/agents/{qgsd-quorum-test-worker.md → nf-quorum-test-worker.md} +1 -1
  14. package/agents/{qgsd-quorum-worker.md → nf-quorum-worker.md} +6 -6
  15. package/agents/{qgsd-research-synthesizer.md → nf-research-synthesizer.md} +5 -5
  16. package/agents/{qgsd-roadmapper.md → nf-roadmapper.md} +3 -3
  17. package/agents/{qgsd-verifier.md → nf-verifier.md} +8 -8
  18. package/bin/accept-debug-invariant.cjs +2 -2
  19. package/bin/account-manager.cjs +10 -10
  20. package/bin/aggregate-requirements.cjs +1 -1
  21. package/bin/analyze-assumptions.cjs +3 -3
  22. package/bin/analyze-state-space.cjs +14 -14
  23. package/bin/assumption-register.cjs +146 -0
  24. package/bin/attribute-trace-divergence.cjs +1 -1
  25. package/bin/auth-drivers/gh-cli.cjs +1 -1
  26. package/bin/auth-drivers/pool.cjs +1 -1
  27. package/bin/autoClosePtoF.cjs +3 -3
  28. package/bin/budget-tracker.cjs +77 -0
  29. package/bin/build-layer-manifest.cjs +153 -0
  30. package/bin/call-quorum-slot.cjs +3 -3
  31. package/bin/ccr-secure-config.cjs +5 -5
  32. package/bin/check-bundled-sdks.cjs +1 -1
  33. package/bin/check-mcp-health.cjs +1 -1
  34. package/bin/check-provider-health.cjs +6 -6
  35. package/bin/check-spec-sync.cjs +26 -26
  36. package/bin/check-trace-schema-drift.cjs +5 -5
  37. package/bin/conformance-schema.cjs +2 -2
  38. package/bin/cross-layer-dashboard.cjs +297 -0
  39. package/bin/design-impact.cjs +377 -0
  40. package/bin/detect-coverage-gaps.cjs +7 -7
  41. package/bin/failure-mode-catalog.cjs +227 -0
  42. package/bin/failure-taxonomy.cjs +177 -0
  43. package/bin/formal-scope-scan.cjs +179 -0
  44. package/bin/gate-a-grounding.cjs +334 -0
  45. package/bin/gate-b-abstraction.cjs +243 -0
  46. package/bin/gate-c-validation.cjs +166 -0
  47. package/bin/generate-formal-specs.cjs +17 -17
  48. package/bin/generate-petri-net.cjs +3 -3
  49. package/bin/generate-tla-cfg.cjs +5 -5
  50. package/bin/git-heatmap.cjs +571 -0
  51. package/bin/harness-diagnostic.cjs +326 -0
  52. package/bin/hazard-model.cjs +261 -0
  53. package/bin/install-formal-tools.cjs +1 -1
  54. package/bin/install.js +184 -139
  55. package/bin/instrumentation-map.cjs +178 -0
  56. package/bin/invariant-catalog.cjs +437 -0
  57. package/bin/issue-classifier.cjs +2 -2
  58. package/bin/load-baseline-requirements.cjs +4 -4
  59. package/bin/manage-agents-core.cjs +32 -32
  60. package/bin/migrate-to-slots.cjs +39 -39
  61. package/bin/mismatch-register.cjs +217 -0
  62. package/bin/nForma.cjs +176 -81
  63. package/bin/{qgsd-solve.cjs → nf-solve.cjs} +327 -14
  64. package/bin/observe-config.cjs +8 -0
  65. package/bin/observe-debt-writer.cjs +1 -1
  66. package/bin/observe-handler-deps.cjs +356 -0
  67. package/bin/observe-handler-grafana.cjs +2 -17
  68. package/bin/observe-handler-internal.cjs +5 -5
  69. package/bin/observe-handler-logstash.cjs +2 -17
  70. package/bin/observe-handler-prometheus.cjs +2 -17
  71. package/bin/observe-handler-upstream.cjs +251 -0
  72. package/bin/observe-handlers.cjs +12 -33
  73. package/bin/observe-render.cjs +68 -22
  74. package/bin/observe-utils.cjs +37 -0
  75. package/bin/observed-fsm.cjs +324 -0
  76. package/bin/planning-paths.cjs +6 -0
  77. package/bin/polyrepo.cjs +1 -1
  78. package/bin/probe-quorum-slots.cjs +1 -1
  79. package/bin/promote-gate-maturity.cjs +274 -0
  80. package/bin/promote-model.cjs +1 -1
  81. package/bin/propose-debug-invariants.cjs +1 -1
  82. package/bin/quorum-cache.cjs +144 -0
  83. package/bin/quorum-consensus-gate.cjs +1 -1
  84. package/bin/quorum-preflight.cjs +89 -0
  85. package/bin/quorum-slot-dispatch.cjs +6 -6
  86. package/bin/requirements-core.cjs +1 -1
  87. package/bin/review-mcp-logs.cjs +1 -1
  88. package/bin/risk-heatmap.cjs +151 -0
  89. package/bin/run-account-manager-tlc.cjs +4 -4
  90. package/bin/run-account-pool-alloy.cjs +2 -2
  91. package/bin/run-alloy.cjs +2 -2
  92. package/bin/run-audit-alloy.cjs +2 -2
  93. package/bin/run-breaker-tlc.cjs +3 -3
  94. package/bin/run-formal-check.cjs +9 -9
  95. package/bin/run-formal-verify.cjs +30 -9
  96. package/bin/run-installer-alloy.cjs +2 -2
  97. package/bin/run-oscillation-tlc.cjs +4 -4
  98. package/bin/run-phase-tlc.cjs +1 -1
  99. package/bin/run-protocol-tlc.cjs +4 -4
  100. package/bin/run-quorum-composition-alloy.cjs +2 -2
  101. package/bin/run-sensitivity-sweep.cjs +2 -2
  102. package/bin/run-stop-hook-tlc.cjs +3 -3
  103. package/bin/run-tlc.cjs +21 -21
  104. package/bin/run-transcript-alloy.cjs +2 -2
  105. package/bin/secrets.cjs +5 -5
  106. package/bin/security-sweep.cjs +238 -0
  107. package/bin/sensitivity-report.cjs +3 -3
  108. package/bin/set-secret.cjs +5 -5
  109. package/bin/setup-telemetry-cron.sh +3 -3
  110. package/bin/stall-detector.cjs +126 -0
  111. package/bin/state-candidates.cjs +206 -0
  112. package/bin/sync-baseline-requirements.cjs +1 -1
  113. package/bin/telemetry-collector.cjs +1 -1
  114. package/bin/test-changed.cjs +111 -0
  115. package/bin/test-recipe-gen.cjs +250 -0
  116. package/bin/trace-corpus-stats.cjs +211 -0
  117. package/bin/unified-mcp-server.mjs +3 -3
  118. package/bin/update-scoreboard.cjs +1 -1
  119. package/bin/validate-memory.cjs +2 -2
  120. package/bin/validate-traces.cjs +10 -10
  121. package/bin/verify-quorum-health.cjs +66 -5
  122. package/bin/xstate-to-tla.cjs +4 -4
  123. package/bin/xstate-trace-walker.cjs +3 -3
  124. package/commands/{qgsd → nf}/add-phase.md +3 -3
  125. package/commands/{qgsd → nf}/add-requirement.md +3 -3
  126. package/commands/{qgsd → nf}/add-todo.md +3 -3
  127. package/commands/{qgsd → nf}/audit-milestone.md +4 -4
  128. package/commands/{qgsd → nf}/check-todos.md +3 -3
  129. package/commands/{qgsd → nf}/cleanup.md +3 -3
  130. package/commands/{qgsd → nf}/close-formal-gaps.md +2 -2
  131. package/commands/{qgsd → nf}/complete-milestone.md +9 -9
  132. package/commands/{qgsd → nf}/debug.md +9 -9
  133. package/commands/{qgsd → nf}/discuss-phase.md +3 -3
  134. package/commands/{qgsd → nf}/execute-phase.md +15 -15
  135. package/commands/{qgsd → nf}/fix-tests.md +3 -3
  136. package/commands/{qgsd → nf}/formal-test-sync.md +1 -1
  137. package/commands/{qgsd → nf}/health.md +3 -3
  138. package/commands/{qgsd → nf}/help.md +3 -3
  139. package/commands/{qgsd → nf}/insert-phase.md +3 -3
  140. package/commands/nf/join-discord.md +18 -0
  141. package/commands/{qgsd → nf}/list-phase-assumptions.md +2 -2
  142. package/commands/{qgsd → nf}/map-codebase.md +7 -7
  143. package/commands/{qgsd → nf}/map-requirements.md +3 -3
  144. package/commands/{qgsd → nf}/mcp-restart.md +3 -3
  145. package/commands/{qgsd → nf}/mcp-set-model.md +8 -8
  146. package/commands/{qgsd → nf}/mcp-setup.md +63 -63
  147. package/commands/{qgsd → nf}/mcp-status.md +3 -3
  148. package/commands/{qgsd → nf}/mcp-update.md +7 -7
  149. package/commands/{qgsd → nf}/new-milestone.md +8 -8
  150. package/commands/{qgsd → nf}/new-project.md +8 -8
  151. package/commands/{qgsd → nf}/observe.md +49 -16
  152. package/commands/{qgsd → nf}/pause-work.md +3 -3
  153. package/commands/{qgsd → nf}/plan-milestone-gaps.md +5 -5
  154. package/commands/{qgsd → nf}/plan-phase.md +6 -6
  155. package/commands/{qgsd → nf}/polyrepo.md +2 -2
  156. package/commands/{qgsd → nf}/progress.md +3 -3
  157. package/commands/{qgsd → nf}/queue.md +2 -2
  158. package/commands/{qgsd → nf}/quick.md +8 -8
  159. package/commands/{qgsd → nf}/quorum-test.md +10 -10
  160. package/commands/{qgsd → nf}/quorum.md +36 -86
  161. package/commands/{qgsd → nf}/reapply-patches.md +2 -2
  162. package/commands/{qgsd → nf}/remove-phase.md +3 -3
  163. package/commands/{qgsd → nf}/research-phase.md +12 -12
  164. package/commands/{qgsd → nf}/resume-work.md +3 -3
  165. package/commands/nf/review-requirements.md +31 -0
  166. package/commands/{qgsd → nf}/set-profile.md +3 -3
  167. package/commands/{qgsd → nf}/settings.md +6 -6
  168. package/commands/{qgsd → nf}/solve.md +35 -35
  169. package/commands/{qgsd → nf}/sync-baselines.md +4 -4
  170. package/commands/{qgsd → nf}/triage.md +10 -10
  171. package/commands/{qgsd → nf}/update.md +3 -3
  172. package/commands/{qgsd → nf}/verify-work.md +5 -5
  173. package/hooks/dist/config-loader.js +188 -32
  174. package/hooks/dist/conformance-schema.cjs +2 -2
  175. package/hooks/dist/gsd-context-monitor.js +118 -13
  176. package/hooks/dist/{qgsd-check-update.js → nf-check-update.js} +5 -5
  177. package/hooks/dist/{qgsd-circuit-breaker.js → nf-circuit-breaker.js} +35 -24
  178. package/hooks/dist/{qgsd-precompact.js → nf-precompact.js} +13 -13
  179. package/hooks/dist/{qgsd-prompt.js → nf-prompt.js} +110 -33
  180. package/hooks/dist/nf-session-start.js +185 -0
  181. package/hooks/dist/{qgsd-slot-correlator.js → nf-slot-correlator.js} +13 -5
  182. package/hooks/dist/{qgsd-spec-regen.js → nf-spec-regen.js} +17 -8
  183. package/hooks/dist/{qgsd-statusline.js → nf-statusline.js} +12 -3
  184. package/hooks/dist/{qgsd-stop.js → nf-stop.js} +152 -18
  185. package/hooks/dist/{qgsd-token-collector.js → nf-token-collector.js} +12 -4
  186. package/hooks/dist/unified-mcp-server.mjs +2 -2
  187. package/package.json +6 -4
  188. package/scripts/build-hooks.js +13 -6
  189. package/scripts/secret-audit.sh +1 -1
  190. package/scripts/verify-hooks-sync.cjs +90 -0
  191. package/templates/{qgsd.json → nf.json} +4 -4
  192. package/commands/qgsd/join-discord.md +0 -18
  193. package/hooks/dist/qgsd-session-start.js +0 -122
@@ -0,0 +1,356 @@
1
+ /**
2
+ * Dependency freshness handler for /nf:observe
3
+ * Auto-detects project ecosystem (Node/Python) and checks for:
4
+ * - Outdated packages (npm outdated / pip list --outdated)
5
+ * - Runtime version (node / python LTS vs current)
6
+ * - Security audit (npm audit / pip-audit)
7
+ *
8
+ * issue_type: 'deps' — rendered in its own DEPENDENCIES table
9
+ */
10
+
11
+ const { execFileSync } = require('node:child_process');
12
+ const fs = require('node:fs');
13
+ const path = require('node:path');
14
+
15
+ /**
16
+ * Detect which ecosystems are present in the project
17
+ * @param {string} basePath
18
+ * @returns {string[]} Array of ecosystem identifiers: 'node', 'python'
19
+ */
20
+ function detectEcosystems(basePath) {
21
+ const base = basePath || process.cwd();
22
+ const ecosystems = [];
23
+ if (fs.existsSync(path.join(base, 'package.json'))) ecosystems.push('node');
24
+ if (
25
+ fs.existsSync(path.join(base, 'requirements.txt')) ||
26
+ fs.existsSync(path.join(base, 'pyproject.toml')) ||
27
+ fs.existsSync(path.join(base, 'Pipfile'))
28
+ ) {
29
+ ecosystems.push('python');
30
+ }
31
+ return ecosystems;
32
+ }
33
+
34
+ /**
35
+ * Parse a semver string into [major, minor, patch]
36
+ * @param {string} ver
37
+ * @returns {number[]}
38
+ */
39
+ function parseSemver(ver) {
40
+ if (!ver) return [0, 0, 0];
41
+ const clean = ver.replace(/^v/, '');
42
+ const parts = clean.split('.').map(Number);
43
+ return [parts[0] || 0, parts[1] || 0, parts[2] || 0];
44
+ }
45
+
46
+ /**
47
+ * Classify severity based on version bump type
48
+ * @param {string} current
49
+ * @param {string} latest
50
+ * @returns {string} 'error' | 'warning' | 'info'
51
+ */
52
+ function classifyVersionBump(current, latest) {
53
+ const [curMaj, curMin] = parseSemver(current);
54
+ const [latMaj, latMin] = parseSemver(latest);
55
+ if (latMaj > curMaj) return 'warning'; // major bump — potential breaking
56
+ if (latMin > curMin) return 'info'; // minor bump — new features
57
+ return 'info'; // patch bump
58
+ }
59
+
60
+ /**
61
+ * Check Node.js outdated packages via npm outdated
62
+ * @param {string} basePath
63
+ * @param {Function} execFn
64
+ * @returns {object[]} Array of dep issue objects
65
+ */
66
+ function checkNpmOutdated(basePath, execFn) {
67
+ const execFile = execFn || execFileSync;
68
+ const issues = [];
69
+
70
+ try {
71
+ // npm outdated exits with code 1 when there are outdated deps — that's expected
72
+ let output;
73
+ try {
74
+ output = execFile('npm', ['outdated', '--json'], { encoding: 'utf8', cwd: basePath });
75
+ } catch (err) {
76
+ // npm outdated returns exit code 1 when outdated packages exist
77
+ output = err.stdout || '';
78
+ }
79
+
80
+ if (!output || !output.trim()) return issues;
81
+ const outdated = JSON.parse(output);
82
+
83
+ for (const [pkg, info] of Object.entries(outdated)) {
84
+ const current = info.current || '?';
85
+ const latest = info.latest || '?';
86
+ const wanted = info.wanted || latest;
87
+ const severity = classifyVersionBump(current, latest);
88
+ const bumpType = (() => {
89
+ const [curMaj] = parseSemver(current);
90
+ const [latMaj, latMin] = parseSemver(latest);
91
+ if (latMaj > curMaj) return 'MAJOR';
92
+ if (latMin > parseSemver(current)[1]) return 'minor';
93
+ return 'patch';
94
+ })();
95
+
96
+ issues.push({
97
+ id: `dep-npm-${pkg}`,
98
+ title: `${pkg} ${current} → ${latest}`,
99
+ severity,
100
+ url: `https://www.npmjs.com/package/${pkg}`,
101
+ age: '',
102
+ created_at: new Date().toISOString(),
103
+ meta: `${bumpType} · wanted ${wanted}`,
104
+ source_type: 'deps',
105
+ issue_type: 'deps',
106
+ _deps: { ecosystem: 'node', pkg, current, latest, wanted, bumpType }
107
+ });
108
+ }
109
+ } catch {
110
+ // npm not available or parse error — silently skip
111
+ }
112
+
113
+ return issues;
114
+ }
115
+
116
+ // Known Node.js LTS major versions — update when new LTS ships
117
+ // See https://nodejs.org/en/about/previous-releases
118
+ const NODE_LTS_MAJOR = 22; // LTS codename "Jod", active until 2027-10
119
+
120
+ /**
121
+ * Check Node.js runtime version against known LTS major
122
+ * Uses a locally maintained constant instead of unreliable npm registry queries.
123
+ * @param {Function} execFn
124
+ * @returns {object|null} Issue object or null if up to date
125
+ */
126
+ function checkNodeVersion(execFn) {
127
+ const execFile = execFn || execFileSync;
128
+ try {
129
+ const current = execFile('node', ['--version'], { encoding: 'utf8' }).trim();
130
+ const [curMaj] = parseSemver(current);
131
+
132
+ if (NODE_LTS_MAJOR > curMaj) {
133
+ return {
134
+ id: 'dep-runtime-node',
135
+ title: `Node.js ${current} → v${NODE_LTS_MAJOR}.x LTS`,
136
+ severity: 'warning',
137
+ url: 'https://nodejs.org/en/download',
138
+ age: '',
139
+ created_at: new Date().toISOString(),
140
+ meta: `runtime · ${NODE_LTS_MAJOR - curMaj} major version(s) behind`,
141
+ source_type: 'deps',
142
+ issue_type: 'deps',
143
+ _deps: { ecosystem: 'node', pkg: 'node', current, latest: `v${NODE_LTS_MAJOR}.x`, bumpType: 'MAJOR' }
144
+ };
145
+ }
146
+ return null;
147
+ } catch {
148
+ return null;
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Check npm audit for known vulnerabilities
154
+ * @param {string} basePath
155
+ * @param {Function} execFn
156
+ * @returns {object[]} Array of vulnerability issue objects
157
+ */
158
+ function checkNpmAudit(basePath, execFn) {
159
+ const execFile = execFn || execFileSync;
160
+ const issues = [];
161
+
162
+ try {
163
+ let output;
164
+ try {
165
+ output = execFile('npm', ['audit', '--json'], { encoding: 'utf8', cwd: basePath });
166
+ } catch (err) {
167
+ output = err.stdout || '';
168
+ }
169
+
170
+ if (!output || !output.trim()) return issues;
171
+ const audit = JSON.parse(output);
172
+ const vulnerabilities = audit.vulnerabilities || {};
173
+
174
+ for (const [pkg, info] of Object.entries(vulnerabilities)) {
175
+ const sevMap = { critical: 'error', high: 'error', moderate: 'warning', low: 'info' };
176
+ const severity = sevMap[info.severity] || 'info';
177
+ const via = Array.isArray(info.via) ? info.via.map(v => typeof v === 'string' ? v : v.title || v.name || '').filter(Boolean).join(', ') : '';
178
+
179
+ issues.push({
180
+ id: `dep-vuln-${pkg}`,
181
+ title: `[VULN] ${pkg}: ${via || info.severity}`,
182
+ severity,
183
+ url: info.url || `https://www.npmjs.com/advisories`,
184
+ age: '',
185
+ created_at: new Date().toISOString(),
186
+ meta: `${info.severity} · ${info.range || 'all versions'} · fix: ${info.fixAvailable ? 'available' : 'none'}`,
187
+ source_type: 'deps',
188
+ issue_type: 'deps',
189
+ _deps: { ecosystem: 'node', pkg, bumpType: 'VULN', severity: info.severity }
190
+ });
191
+ }
192
+ } catch {
193
+ // npm audit not available — skip
194
+ }
195
+
196
+ return issues;
197
+ }
198
+
199
+ /**
200
+ * Check Python outdated packages via pip
201
+ * @param {string} basePath
202
+ * @param {Function} execFn
203
+ * @returns {object[]} Array of dep issue objects
204
+ */
205
+ function checkPipOutdated(basePath, execFn) {
206
+ const execFile = execFn || execFileSync;
207
+ const issues = [];
208
+
209
+ try {
210
+ const output = execFile('pip', ['list', '--outdated', '--format=json'], { encoding: 'utf8', cwd: basePath });
211
+ const outdated = JSON.parse(output);
212
+
213
+ for (const pkg of outdated) {
214
+ const current = pkg.version || '?';
215
+ const latest = pkg.latest_version || '?';
216
+ const severity = classifyVersionBump(current, latest);
217
+ const bumpType = (() => {
218
+ const [curMaj] = parseSemver(current);
219
+ const [latMaj, latMin] = parseSemver(latest);
220
+ if (latMaj > curMaj) return 'MAJOR';
221
+ if (latMin > parseSemver(current)[1]) return 'minor';
222
+ return 'patch';
223
+ })();
224
+
225
+ issues.push({
226
+ id: `dep-pip-${pkg.name}`,
227
+ title: `${pkg.name} ${current} → ${latest}`,
228
+ severity,
229
+ url: `https://pypi.org/project/${pkg.name}/`,
230
+ age: '',
231
+ created_at: new Date().toISOString(),
232
+ meta: `${bumpType} · ${pkg.latest_filetype || 'wheel'}`,
233
+ source_type: 'deps',
234
+ issue_type: 'deps',
235
+ _deps: { ecosystem: 'python', pkg: pkg.name, current, latest, bumpType }
236
+ });
237
+ }
238
+ } catch {
239
+ // pip not available — skip
240
+ }
241
+
242
+ return issues;
243
+ }
244
+
245
+ // Known Python minimum recommended minor version — update when new stable ships
246
+ // See https://devguide.python.org/versions/
247
+ const PYTHON_MIN_MINOR = 12; // 3.12+ recommended (3.11 security-only since 2025-10)
248
+
249
+ /**
250
+ * Check Python runtime version against minimum recommended
251
+ * Uses a locally maintained constant for the threshold.
252
+ * @param {Function} execFn
253
+ * @returns {object|null}
254
+ */
255
+ function checkPythonVersion(execFn) {
256
+ const execFile = execFn || execFileSync;
257
+ try {
258
+ const output = execFile('python3', ['--version'], { encoding: 'utf8' }).trim();
259
+ const current = output.replace(/^Python\s+/, '');
260
+ const [curMaj, curMin] = parseSemver(current);
261
+ if (curMaj === 3 && curMin < PYTHON_MIN_MINOR) {
262
+ return {
263
+ id: 'dep-runtime-python',
264
+ title: `Python ${current} → 3.${PYTHON_MIN_MINOR}+`,
265
+ severity: 'info',
266
+ url: 'https://www.python.org/downloads/',
267
+ age: '',
268
+ created_at: new Date().toISOString(),
269
+ meta: `runtime · consider upgrading`,
270
+ source_type: 'deps',
271
+ issue_type: 'deps',
272
+ _deps: { ecosystem: 'python', pkg: 'python', current, latest: `3.${PYTHON_MIN_MINOR}+`, bumpType: 'MAJOR' }
273
+ };
274
+ }
275
+ return null;
276
+ } catch {
277
+ return null;
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Deps source handler
283
+ * Auto-detects ecosystem and checks outdated packages, runtime version, and audit
284
+ *
285
+ * @param {object} sourceConfig - { type: 'deps', label, ecosystems?: ['node','python'], skip_audit?: boolean }
286
+ * @param {object} options - { execFn?, basePath? }
287
+ * @returns {object} Standard observe schema result
288
+ */
289
+ function handleDeps(sourceConfig, options) {
290
+ const label = sourceConfig.label || 'Dependencies';
291
+ const basePath = options.basePath || process.cwd();
292
+ const execFile = options.execFn || execFileSync;
293
+ const skipAudit = sourceConfig.skip_audit || false;
294
+
295
+ try {
296
+ // Auto-detect or use configured ecosystems
297
+ const ecosystems = sourceConfig.ecosystems
298
+ ? (Array.isArray(sourceConfig.ecosystems) ? sourceConfig.ecosystems : [sourceConfig.ecosystems])
299
+ : detectEcosystems(basePath);
300
+
301
+ if (ecosystems.length === 0) {
302
+ return {
303
+ source_label: label,
304
+ source_type: 'deps',
305
+ status: 'ok',
306
+ issues: []
307
+ };
308
+ }
309
+
310
+ const issues = [];
311
+
312
+ if (ecosystems.includes('node')) {
313
+ issues.push(...checkNpmOutdated(basePath, execFile));
314
+ const nodeVer = checkNodeVersion(execFile);
315
+ if (nodeVer) issues.push(nodeVer);
316
+ if (!skipAudit) {
317
+ issues.push(...checkNpmAudit(basePath, execFile));
318
+ }
319
+ }
320
+
321
+ if (ecosystems.includes('python')) {
322
+ issues.push(...checkPipOutdated(basePath, execFile));
323
+ const pyVer = checkPythonVersion(execFile);
324
+ if (pyVer) issues.push(pyVer);
325
+ }
326
+
327
+ return {
328
+ source_label: label,
329
+ source_type: 'deps',
330
+ status: 'ok',
331
+ issues
332
+ };
333
+ } catch (err) {
334
+ return {
335
+ source_label: label,
336
+ source_type: 'deps',
337
+ status: 'error',
338
+ error: `Deps check failed: ${err.message}`,
339
+ issues: []
340
+ };
341
+ }
342
+ }
343
+
344
+ module.exports = {
345
+ handleDeps,
346
+ detectEcosystems,
347
+ checkNpmOutdated,
348
+ checkNpmAudit,
349
+ checkNodeVersion,
350
+ checkPipOutdated,
351
+ checkPythonVersion,
352
+ classifyVersionBump,
353
+ parseSemver,
354
+ NODE_LTS_MAJOR,
355
+ PYTHON_MIN_MINOR
356
+ };
@@ -1,25 +1,10 @@
1
1
  /**
2
- * Grafana source handler for /qgsd:observe
2
+ * Grafana source handler for /nf:observe
3
3
  * Fetches alert rules from Grafana unified alerting API
4
4
  * Returns standard issue schema for the observe registry
5
5
  */
6
6
 
7
- /**
8
- * Format age from ISO date to human-readable string
9
- * @param {string} isoDate - ISO8601 date string
10
- * @returns {string} Human-readable age
11
- */
12
- function formatAge(isoDate) {
13
- if (!isoDate) return 'unknown';
14
- const diffMs = Date.now() - new Date(isoDate).getTime();
15
- if (diffMs < 0) return 'future';
16
- const minutes = Math.floor(diffMs / 60000);
17
- if (minutes < 60) return `${minutes}m`;
18
- const hours = Math.floor(minutes / 60);
19
- if (hours < 24) return `${hours}h`;
20
- const days = Math.floor(hours / 24);
21
- return `${days}d`;
22
- }
7
+ const { formatAge } = require('./observe-utils.cjs');
23
8
 
24
9
  /**
25
10
  * Map Grafana alert state to severity
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Internal work detection handler for /qgsd:observe
2
+ * Internal work detection handler for /nf:observe
3
3
  * Scans local project state for:
4
4
  * 1. Unfinished quick tasks (PLAN.md without SUMMARY.md)
5
5
  * 2. Stale debug sessions (quorum-debug-latest.md)
@@ -77,7 +77,7 @@ function handleInternal(sourceConfig, options) {
77
77
  meta: 'PLAN exists, no SUMMARY',
78
78
  source_type: 'internal',
79
79
  issue_type: 'issue',
80
- _route: `/qgsd:quick "${slug}"`
80
+ _route: `/nf:quick "${slug}"`
81
81
  });
82
82
  }
83
83
  }
@@ -113,7 +113,7 @@ function handleInternal(sourceConfig, options) {
113
113
  meta: 'Debug session may need resolution',
114
114
  source_type: 'internal',
115
115
  issue_type: 'issue',
116
- _route: '/qgsd:debug --resume'
116
+ _route: '/nf:debug --resume'
117
117
  });
118
118
  }
119
119
  }
@@ -222,7 +222,7 @@ function handleInternal(sourceConfig, options) {
222
222
  issue_type: 'issue',
223
223
  exception_type: tag,
224
224
  function_name: relPath,
225
- _route: `/qgsd:quick "Resolve ${tag} at ${relPath}:${lineNum}"`
225
+ _route: `/nf:quick "Resolve ${tag} at ${relPath}:${lineNum}"`
226
226
  });
227
227
  count++;
228
228
  }
@@ -268,7 +268,7 @@ function handleInternal(sourceConfig, options) {
268
268
  meta: 'Phase active in STATE.md but no VERIFICATION.md found',
269
269
  source_type: 'internal',
270
270
  issue_type: 'issue',
271
- _route: '/qgsd:solve'
271
+ _route: '/nf:solve'
272
272
  });
273
273
  }
274
274
  }
@@ -1,25 +1,10 @@
1
1
  /**
2
- * Logstash/Elasticsearch source handler for /qgsd:observe
2
+ * Logstash/Elasticsearch source handler for /nf:observe
3
3
  * Queries Elasticsearch indices for log entries matching severity filters
4
4
  * Returns standard issue schema for the observe registry
5
5
  */
6
6
 
7
- /**
8
- * Format age from ISO date to human-readable string
9
- * @param {string} isoDate - ISO8601 date string
10
- * @returns {string} Human-readable age
11
- */
12
- function formatAge(isoDate) {
13
- if (!isoDate) return 'unknown';
14
- const diffMs = Date.now() - new Date(isoDate).getTime();
15
- if (diffMs < 0) return 'future';
16
- const minutes = Math.floor(diffMs / 60000);
17
- if (minutes < 60) return `${minutes}m`;
18
- const hours = Math.floor(minutes / 60);
19
- if (hours < 24) return `${hours}h`;
20
- const days = Math.floor(hours / 24);
21
- return `${days}d`;
22
- }
7
+ const { formatAge } = require('./observe-utils.cjs');
23
8
 
24
9
  /**
25
10
  * Normalize log level to standard severity
@@ -1,25 +1,10 @@
1
1
  /**
2
- * Prometheus source handler for /qgsd:observe
2
+ * Prometheus source handler for /nf:observe
3
3
  * Supports: /api/v1/alerts (active alerts) and /api/v1/query (PromQL)
4
4
  * Returns standard issue schema for the observe registry
5
5
  */
6
6
 
7
- /**
8
- * Format age from ISO date to human-readable string
9
- * @param {string} isoDate - ISO8601 date string
10
- * @returns {string} Human-readable age
11
- */
12
- function formatAge(isoDate) {
13
- if (!isoDate) return 'unknown';
14
- const diffMs = Date.now() - new Date(isoDate).getTime();
15
- if (diffMs < 0) return 'future';
16
- const minutes = Math.floor(diffMs / 60000);
17
- if (minutes < 60) return `${minutes}m`;
18
- const hours = Math.floor(minutes / 60);
19
- if (hours < 24) return `${hours}h`;
20
- const days = Math.floor(hours / 24);
21
- return `${days}d`;
22
- }
7
+ const { formatAge } = require('./observe-utils.cjs');
23
8
 
24
9
  /**
25
10
  * Prometheus source handler