@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
|
@@ -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 /
|
|
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 /
|
|
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: `/
|
|
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: '/
|
|
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: `/
|
|
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: '/
|
|
271
|
+
_route: '/nf:solve'
|
|
272
272
|
});
|
|
273
273
|
}
|
|
274
274
|
}
|
|
@@ -1,25 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Logstash/Elasticsearch source handler for /
|
|
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 /
|
|
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
|