@nforma.ai/nforma 0.2.1
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/LICENSE +22 -0
- package/README.md +1024 -0
- package/agents/qgsd-codebase-mapper.md +764 -0
- package/agents/qgsd-debugger.md +1201 -0
- package/agents/qgsd-executor.md +472 -0
- package/agents/qgsd-integration-checker.md +443 -0
- package/agents/qgsd-phase-researcher.md +502 -0
- package/agents/qgsd-plan-checker.md +643 -0
- package/agents/qgsd-planner.md +1182 -0
- package/agents/qgsd-project-researcher.md +621 -0
- package/agents/qgsd-quorum-orchestrator.md +628 -0
- package/agents/qgsd-quorum-slot-worker.md +41 -0
- package/agents/qgsd-quorum-synthesizer.md +133 -0
- package/agents/qgsd-quorum-test-worker.md +37 -0
- package/agents/qgsd-quorum-worker.md +161 -0
- package/agents/qgsd-research-synthesizer.md +239 -0
- package/agents/qgsd-roadmapper.md +660 -0
- package/agents/qgsd-verifier.md +628 -0
- package/bin/accept-debug-invariant.cjs +165 -0
- package/bin/account-manager.cjs +719 -0
- package/bin/aggregate-requirements.cjs +466 -0
- package/bin/analyze-assumptions.cjs +757 -0
- package/bin/analyze-state-space.cjs +921 -0
- package/bin/attribute-trace-divergence.cjs +150 -0
- package/bin/auth-drivers/gh-cli.cjs +93 -0
- package/bin/auth-drivers/index.cjs +46 -0
- package/bin/auth-drivers/pool.cjs +67 -0
- package/bin/auth-drivers/simple.cjs +95 -0
- package/bin/autoClosePtoF.cjs +110 -0
- package/bin/blessed-terminal.cjs +350 -0
- package/bin/build-phase-index.cjs +472 -0
- package/bin/call-quorum-slot.cjs +541 -0
- package/bin/ccr-secure-config.cjs +99 -0
- package/bin/ccr-secure-start.cjs +83 -0
- package/bin/check-bundled-sdks.cjs +177 -0
- package/bin/check-coverage-guard.cjs +112 -0
- package/bin/check-liveness-fairness.cjs +95 -0
- package/bin/check-mcp-health.cjs +123 -0
- package/bin/check-provider-health.cjs +395 -0
- package/bin/check-results-exit.cjs +24 -0
- package/bin/check-spec-sync.cjs +360 -0
- package/bin/check-trace-redaction.cjs +271 -0
- package/bin/check-trace-schema-drift.cjs +99 -0
- package/bin/compareDrift.cjs +21 -0
- package/bin/conformance-schema.cjs +12 -0
- package/bin/count-scenarios.cjs +420 -0
- package/bin/debt-dedup.cjs +144 -0
- package/bin/debt-ledger.cjs +61 -0
- package/bin/debt-retention.cjs +76 -0
- package/bin/debt-state-machine.cjs +80 -0
- package/bin/detect-coverage-gaps.cjs +204 -0
- package/bin/detect-project-intent.cjs +362 -0
- package/bin/export-prism-constants.cjs +164 -0
- package/bin/extract-annotations.cjs +633 -0
- package/bin/extractFormalExpected.cjs +104 -0
- package/bin/fingerprint-drift.cjs +24 -0
- package/bin/fingerprint-issue.cjs +46 -0
- package/bin/formal-core.cjs +519 -0
- package/bin/formal-ref-linker.cjs +141 -0
- package/bin/formal-test-sync.cjs +788 -0
- package/bin/generate-formal-specs.cjs +588 -0
- package/bin/generate-petri-net.cjs +397 -0
- package/bin/generate-phase-spec.cjs +249 -0
- package/bin/generate-proposed-changes.cjs +194 -0
- package/bin/generate-tla-cfg.cjs +122 -0
- package/bin/generate-traceability-matrix.cjs +701 -0
- package/bin/generate-triage-bundle.cjs +300 -0
- package/bin/gh-account-rotate.cjs +34 -0
- package/bin/initialize-model-registry.cjs +105 -0
- package/bin/install-formal-tools.cjs +382 -0
- package/bin/install.js +2424 -0
- package/bin/isNumericThreshold.cjs +34 -0
- package/bin/issue-classifier.cjs +151 -0
- package/bin/levenshtein.cjs +74 -0
- package/bin/lint-formal-models.cjs +580 -0
- package/bin/load-baseline-requirements.cjs +275 -0
- package/bin/manage-agents-core.cjs +815 -0
- package/bin/migrate-formal-dir.cjs +172 -0
- package/bin/migrate-planning.cjs +206 -0
- package/bin/migrate-to-slots.cjs +255 -0
- package/bin/nForma.cjs +2726 -0
- package/bin/observe-config.cjs +353 -0
- package/bin/observe-debt-writer.cjs +140 -0
- package/bin/observe-handler-grafana.cjs +128 -0
- package/bin/observe-handler-internal.cjs +301 -0
- package/bin/observe-handler-logstash.cjs +153 -0
- package/bin/observe-handler-prometheus.cjs +185 -0
- package/bin/observe-handlers.cjs +436 -0
- package/bin/observe-registry.cjs +131 -0
- package/bin/observe-render.cjs +168 -0
- package/bin/planning-paths.cjs +167 -0
- package/bin/polyrepo.cjs +560 -0
- package/bin/prism-priority.cjs +153 -0
- package/bin/probe-quorum-slots.cjs +167 -0
- package/bin/promote-model.cjs +225 -0
- package/bin/propose-debug-invariants.cjs +165 -0
- package/bin/providers.json +392 -0
- package/bin/pty-proxy.py +129 -0
- package/bin/qgsd-solve.cjs +2477 -0
- package/bin/quorum-consensus-gate.cjs +238 -0
- package/bin/quorum-formal-context.cjs +183 -0
- package/bin/quorum-slot-dispatch.cjs +934 -0
- package/bin/read-policy.cjs +60 -0
- package/bin/requirement-map.cjs +63 -0
- package/bin/requirements-core.cjs +247 -0
- package/bin/resolve-cli.cjs +101 -0
- package/bin/review-mcp-logs.cjs +294 -0
- package/bin/run-account-manager-tlc.cjs +188 -0
- package/bin/run-account-pool-alloy.cjs +158 -0
- package/bin/run-alloy.cjs +153 -0
- package/bin/run-audit-alloy.cjs +187 -0
- package/bin/run-breaker-tlc.cjs +181 -0
- package/bin/run-formal-check.cjs +395 -0
- package/bin/run-formal-verify.cjs +701 -0
- package/bin/run-installer-alloy.cjs +188 -0
- package/bin/run-oauth-rotation-prism.cjs +132 -0
- package/bin/run-oscillation-tlc.cjs +202 -0
- package/bin/run-phase-tlc.cjs +228 -0
- package/bin/run-prism.cjs +446 -0
- package/bin/run-protocol-tlc.cjs +201 -0
- package/bin/run-quorum-composition-alloy.cjs +155 -0
- package/bin/run-sensitivity-sweep.cjs +231 -0
- package/bin/run-stop-hook-tlc.cjs +188 -0
- package/bin/run-tlc.cjs +467 -0
- package/bin/run-transcript-alloy.cjs +173 -0
- package/bin/run-uppaal.cjs +264 -0
- package/bin/secrets.cjs +134 -0
- package/bin/sensitivity-report.cjs +219 -0
- package/bin/sensitivity-sweep-feedback.cjs +194 -0
- package/bin/set-secret.cjs +29 -0
- package/bin/setup-telemetry-cron.sh +36 -0
- package/bin/sweepPtoF.cjs +63 -0
- package/bin/sync-baseline-requirements.cjs +290 -0
- package/bin/task-envelope.cjs +360 -0
- package/bin/telemetry-collector.cjs +229 -0
- package/bin/unified-mcp-server.mjs +735 -0
- package/bin/update-agents.cjs +369 -0
- package/bin/update-scoreboard.cjs +1134 -0
- package/bin/validate-debt-entry.cjs +207 -0
- package/bin/validate-invariant.cjs +419 -0
- package/bin/validate-memory.cjs +389 -0
- package/bin/validate-requirements-haiku.cjs +435 -0
- package/bin/validate-traces.cjs +438 -0
- package/bin/verify-formal-results.cjs +124 -0
- package/bin/verify-quorum-health.cjs +273 -0
- package/bin/write-check-result.cjs +106 -0
- package/bin/xstate-to-tla.cjs +483 -0
- package/bin/xstate-trace-walker.cjs +205 -0
- package/commands/qgsd/add-phase.md +43 -0
- package/commands/qgsd/add-requirement.md +24 -0
- package/commands/qgsd/add-todo.md +47 -0
- package/commands/qgsd/audit-milestone.md +37 -0
- package/commands/qgsd/check-todos.md +45 -0
- package/commands/qgsd/cleanup.md +18 -0
- package/commands/qgsd/close-formal-gaps.md +33 -0
- package/commands/qgsd/complete-milestone.md +136 -0
- package/commands/qgsd/debug.md +166 -0
- package/commands/qgsd/discuss-phase.md +83 -0
- package/commands/qgsd/execute-phase.md +117 -0
- package/commands/qgsd/fix-tests.md +27 -0
- package/commands/qgsd/formal-test-sync.md +32 -0
- package/commands/qgsd/health.md +22 -0
- package/commands/qgsd/help.md +22 -0
- package/commands/qgsd/insert-phase.md +32 -0
- package/commands/qgsd/join-discord.md +18 -0
- package/commands/qgsd/list-phase-assumptions.md +46 -0
- package/commands/qgsd/map-codebase.md +71 -0
- package/commands/qgsd/map-requirements.md +20 -0
- package/commands/qgsd/mcp-restart.md +176 -0
- package/commands/qgsd/mcp-set-model.md +134 -0
- package/commands/qgsd/mcp-setup.md +1371 -0
- package/commands/qgsd/mcp-status.md +274 -0
- package/commands/qgsd/mcp-update.md +238 -0
- package/commands/qgsd/new-milestone.md +44 -0
- package/commands/qgsd/new-project.md +42 -0
- package/commands/qgsd/observe.md +260 -0
- package/commands/qgsd/pause-work.md +38 -0
- package/commands/qgsd/plan-milestone-gaps.md +34 -0
- package/commands/qgsd/plan-phase.md +44 -0
- package/commands/qgsd/polyrepo.md +50 -0
- package/commands/qgsd/progress.md +24 -0
- package/commands/qgsd/queue.md +54 -0
- package/commands/qgsd/quick.md +133 -0
- package/commands/qgsd/quorum-test.md +275 -0
- package/commands/qgsd/quorum.md +707 -0
- package/commands/qgsd/reapply-patches.md +110 -0
- package/commands/qgsd/remove-phase.md +31 -0
- package/commands/qgsd/research-phase.md +189 -0
- package/commands/qgsd/resume-work.md +40 -0
- package/commands/qgsd/set-profile.md +34 -0
- package/commands/qgsd/settings.md +39 -0
- package/commands/qgsd/solve.md +565 -0
- package/commands/qgsd/sync-baselines.md +119 -0
- package/commands/qgsd/triage.md +233 -0
- package/commands/qgsd/update.md +37 -0
- package/commands/qgsd/verify-work.md +38 -0
- package/hooks/dist/config-loader.js +297 -0
- package/hooks/dist/conformance-schema.cjs +12 -0
- package/hooks/dist/gsd-context-monitor.js +64 -0
- package/hooks/dist/qgsd-check-update.js +62 -0
- package/hooks/dist/qgsd-circuit-breaker.js +682 -0
- package/hooks/dist/qgsd-precompact.js +156 -0
- package/hooks/dist/qgsd-prompt.js +653 -0
- package/hooks/dist/qgsd-session-start.js +122 -0
- package/hooks/dist/qgsd-slot-correlator.js +58 -0
- package/hooks/dist/qgsd-spec-regen.js +86 -0
- package/hooks/dist/qgsd-statusline.js +91 -0
- package/hooks/dist/qgsd-stop.js +553 -0
- package/hooks/dist/qgsd-token-collector.js +133 -0
- package/hooks/dist/unified-mcp-server.mjs +669 -0
- package/package.json +95 -0
- package/scripts/build-hooks.js +46 -0
- package/scripts/postinstall.js +48 -0
- package/scripts/secret-audit.sh +45 -0
- package/templates/qgsd.json +49 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Scan a repository for signals indicating project intent.
|
|
9
|
+
* Returns suggested intent with confidence levels and confirmation needs.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} rootPath - Path to project root
|
|
12
|
+
* @returns {Object} { suggested, signals, needs_confirmation }
|
|
13
|
+
*/
|
|
14
|
+
function detectProjectIntent(rootPath) {
|
|
15
|
+
const root = rootPath || process.cwd();
|
|
16
|
+
|
|
17
|
+
const signals = [];
|
|
18
|
+
const suggested = {
|
|
19
|
+
base_profile: 'unknown',
|
|
20
|
+
iac: false,
|
|
21
|
+
deploy: 'none',
|
|
22
|
+
sensitive: false,
|
|
23
|
+
oss: false,
|
|
24
|
+
monorepo: false,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Helper to check if file exists
|
|
28
|
+
const fileExists = (filePath) => {
|
|
29
|
+
try {
|
|
30
|
+
return fs.existsSync(path.join(root, filePath));
|
|
31
|
+
} catch (_) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Helper to check if any file matching a pattern exists
|
|
37
|
+
const globExists = (pattern) => {
|
|
38
|
+
try {
|
|
39
|
+
const dir = path.join(root, path.dirname(pattern));
|
|
40
|
+
if (!fs.existsSync(dir)) return false;
|
|
41
|
+
const filename = path.basename(pattern);
|
|
42
|
+
const files = fs.readdirSync(dir);
|
|
43
|
+
const regex = filename.replace(/\*/g, '.*');
|
|
44
|
+
return files.some(f => new RegExp(`^${regex}$`).test(f));
|
|
45
|
+
} catch (_) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Helper to read package.json
|
|
51
|
+
const getPackageJson = () => {
|
|
52
|
+
try {
|
|
53
|
+
if (fileExists('package.json')) {
|
|
54
|
+
const content = fs.readFileSync(path.join(root, 'package.json'), 'utf8');
|
|
55
|
+
return JSON.parse(content);
|
|
56
|
+
}
|
|
57
|
+
} catch (_) {}
|
|
58
|
+
return null;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// base_profile detection
|
|
63
|
+
// ============================================================================
|
|
64
|
+
|
|
65
|
+
const pkg = getPackageJson();
|
|
66
|
+
let baseProfileFound = false;
|
|
67
|
+
|
|
68
|
+
if (pkg) {
|
|
69
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
70
|
+
const depsStr = JSON.stringify(deps);
|
|
71
|
+
|
|
72
|
+
// web frameworks
|
|
73
|
+
if (/next|nuxt|vite|gatsby|remix/.test(depsStr)) {
|
|
74
|
+
suggested.base_profile = 'web';
|
|
75
|
+
signals.push({
|
|
76
|
+
dimension: 'base_profile',
|
|
77
|
+
confidence: 'medium',
|
|
78
|
+
evidence: ['Web framework detected in package.json (next/nuxt/vite/gatsby/remix)'],
|
|
79
|
+
});
|
|
80
|
+
baseProfileFound = true;
|
|
81
|
+
}
|
|
82
|
+
// mobile frameworks
|
|
83
|
+
else if (/react-native|expo/.test(depsStr)) {
|
|
84
|
+
suggested.base_profile = 'mobile';
|
|
85
|
+
signals.push({
|
|
86
|
+
dimension: 'base_profile',
|
|
87
|
+
confidence: 'medium',
|
|
88
|
+
evidence: ['Mobile framework detected in package.json (react-native/expo)'],
|
|
89
|
+
});
|
|
90
|
+
baseProfileFound = true;
|
|
91
|
+
}
|
|
92
|
+
// desktop frameworks
|
|
93
|
+
else if (/electron|tauri/.test(depsStr)) {
|
|
94
|
+
suggested.base_profile = 'desktop';
|
|
95
|
+
signals.push({
|
|
96
|
+
dimension: 'base_profile',
|
|
97
|
+
confidence: 'medium',
|
|
98
|
+
evidence: ['Desktop framework detected in package.json (electron/tauri)'],
|
|
99
|
+
});
|
|
100
|
+
baseProfileFound = true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check for bin field (CLI)
|
|
104
|
+
if (pkg.bin && !baseProfileFound) {
|
|
105
|
+
suggested.base_profile = 'cli';
|
|
106
|
+
signals.push({
|
|
107
|
+
dimension: 'base_profile',
|
|
108
|
+
confidence: 'medium',
|
|
109
|
+
evidence: ['bin field present in package.json'],
|
|
110
|
+
});
|
|
111
|
+
baseProfileFound = true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check for OpenAPI (API)
|
|
115
|
+
if (!baseProfileFound && (fileExists('openapi.json') || fileExists('openapi.yaml') || fileExists('swagger.json'))) {
|
|
116
|
+
suggested.base_profile = 'api';
|
|
117
|
+
signals.push({
|
|
118
|
+
dimension: 'base_profile',
|
|
119
|
+
confidence: 'medium',
|
|
120
|
+
evidence: ['OpenAPI/Swagger spec found (openapi.json/openapi.yaml/swagger.json)'],
|
|
121
|
+
});
|
|
122
|
+
baseProfileFound = true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!baseProfileFound) {
|
|
127
|
+
signals.push({
|
|
128
|
+
dimension: 'base_profile',
|
|
129
|
+
confidence: 'low',
|
|
130
|
+
evidence: ['No framework or project markers detected'],
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ============================================================================
|
|
135
|
+
// IaC detection
|
|
136
|
+
// ============================================================================
|
|
137
|
+
|
|
138
|
+
const iacSignals = [];
|
|
139
|
+
|
|
140
|
+
if (globExists('*.tf')) {
|
|
141
|
+
iacSignals.push('Terraform files (*.tf) found');
|
|
142
|
+
}
|
|
143
|
+
if (fileExists('terraform/main.tf')) {
|
|
144
|
+
iacSignals.push('terraform/main.tf found');
|
|
145
|
+
}
|
|
146
|
+
if (fileExists('Pulumi.yaml')) {
|
|
147
|
+
iacSignals.push('Pulumi.yaml found');
|
|
148
|
+
}
|
|
149
|
+
if (fileExists('cdk.json')) {
|
|
150
|
+
iacSignals.push('cdk.json (AWS CDK) found');
|
|
151
|
+
}
|
|
152
|
+
if (fileExists('serverless.yml')) {
|
|
153
|
+
iacSignals.push('serverless.yml found');
|
|
154
|
+
}
|
|
155
|
+
if (fileExists('infra/') && fs.statSync(path.join(root, 'infra')).isDirectory()) {
|
|
156
|
+
iacSignals.push('infra/ directory found');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (iacSignals.length > 0) {
|
|
160
|
+
suggested.iac = true;
|
|
161
|
+
signals.push({
|
|
162
|
+
dimension: 'iac',
|
|
163
|
+
confidence: 'high',
|
|
164
|
+
evidence: iacSignals,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ============================================================================
|
|
169
|
+
// deploy detection
|
|
170
|
+
// ============================================================================
|
|
171
|
+
|
|
172
|
+
const deploySignals = [];
|
|
173
|
+
|
|
174
|
+
if (fileExists('Dockerfile')) {
|
|
175
|
+
suggested.deploy = 'docker';
|
|
176
|
+
deploySignals.push('Dockerfile found');
|
|
177
|
+
} else if (fileExists('docker-compose.yml')) {
|
|
178
|
+
suggested.deploy = 'docker';
|
|
179
|
+
deploySignals.push('docker-compose.yml found');
|
|
180
|
+
} else if (fileExists('fly.toml')) {
|
|
181
|
+
suggested.deploy = 'fly';
|
|
182
|
+
deploySignals.push('fly.toml found');
|
|
183
|
+
} else if (fileExists('vercel.json')) {
|
|
184
|
+
suggested.deploy = 'vercel';
|
|
185
|
+
deploySignals.push('vercel.json found');
|
|
186
|
+
} else if (fileExists('Procfile')) {
|
|
187
|
+
suggested.deploy = 'heroku';
|
|
188
|
+
deploySignals.push('Procfile found');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (deploySignals.length > 0) {
|
|
192
|
+
signals.push({
|
|
193
|
+
dimension: 'deploy',
|
|
194
|
+
confidence: 'high',
|
|
195
|
+
evidence: deploySignals,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ============================================================================
|
|
200
|
+
// sensitive detection
|
|
201
|
+
// ============================================================================
|
|
202
|
+
|
|
203
|
+
const sensitiveSignals = [];
|
|
204
|
+
|
|
205
|
+
if (pkg) {
|
|
206
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
207
|
+
const depsStr = JSON.stringify(deps);
|
|
208
|
+
|
|
209
|
+
if (/passport|next-auth|auth0|firebase|okta/.test(depsStr)) {
|
|
210
|
+
sensitiveSignals.push('Auth library detected (passport/next-auth/auth0/firebase/okta)');
|
|
211
|
+
}
|
|
212
|
+
if (/stripe|paypal|square|shopify|commerce/.test(depsStr)) {
|
|
213
|
+
sensitiveSignals.push('Payment library detected (stripe/paypal/square/shopify)');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (sensitiveSignals.length > 0) {
|
|
218
|
+
suggested.sensitive = true;
|
|
219
|
+
signals.push({
|
|
220
|
+
dimension: 'sensitive',
|
|
221
|
+
confidence: 'medium',
|
|
222
|
+
evidence: sensitiveSignals,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ============================================================================
|
|
227
|
+
// oss detection
|
|
228
|
+
// ============================================================================
|
|
229
|
+
|
|
230
|
+
const ossSignals = [];
|
|
231
|
+
|
|
232
|
+
if (fileExists('LICENSE')) {
|
|
233
|
+
ossSignals.push('LICENSE file found');
|
|
234
|
+
}
|
|
235
|
+
if (fileExists('CONTRIBUTING.md')) {
|
|
236
|
+
ossSignals.push('CONTRIBUTING.md found');
|
|
237
|
+
}
|
|
238
|
+
if (fileExists('CODE_OF_CONDUCT.md')) {
|
|
239
|
+
ossSignals.push('CODE_OF_CONDUCT.md found');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (ossSignals.length > 0) {
|
|
243
|
+
suggested.oss = true;
|
|
244
|
+
signals.push({
|
|
245
|
+
dimension: 'oss',
|
|
246
|
+
confidence: 'high',
|
|
247
|
+
evidence: ossSignals,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ============================================================================
|
|
252
|
+
// monorepo detection
|
|
253
|
+
// ============================================================================
|
|
254
|
+
|
|
255
|
+
const monorepoSignals = [];
|
|
256
|
+
|
|
257
|
+
if (fileExists('pnpm-workspace.yaml')) {
|
|
258
|
+
monorepoSignals.push('pnpm-workspace.yaml found');
|
|
259
|
+
}
|
|
260
|
+
if (fileExists('lerna.json')) {
|
|
261
|
+
monorepoSignals.push('lerna.json found');
|
|
262
|
+
}
|
|
263
|
+
if (fileExists('nx.json')) {
|
|
264
|
+
monorepoSignals.push('nx.json found');
|
|
265
|
+
}
|
|
266
|
+
if (fileExists('turbo.json')) {
|
|
267
|
+
monorepoSignals.push('turbo.json found');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (monorepoSignals.length > 0) {
|
|
271
|
+
suggested.monorepo = true;
|
|
272
|
+
signals.push({
|
|
273
|
+
dimension: 'monorepo',
|
|
274
|
+
confidence: 'high',
|
|
275
|
+
evidence: monorepoSignals,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ============================================================================
|
|
280
|
+
// Build needs_confirmation array
|
|
281
|
+
// ============================================================================
|
|
282
|
+
|
|
283
|
+
const needsConfirmation = [];
|
|
284
|
+
|
|
285
|
+
// Medium-confidence dimensions need confirmation
|
|
286
|
+
for (const signal of signals) {
|
|
287
|
+
if (signal.confidence === 'medium') {
|
|
288
|
+
needsConfirmation.push(signal.dimension);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Unknown base_profile always needs confirmation
|
|
293
|
+
if (suggested.base_profile === 'unknown') {
|
|
294
|
+
if (!needsConfirmation.includes('base_profile')) {
|
|
295
|
+
needsConfirmation.push('base_profile');
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
suggested,
|
|
301
|
+
signals,
|
|
302
|
+
needs_confirmation: needsConfirmation,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ============================================================================
|
|
307
|
+
// CLI mode
|
|
308
|
+
// ============================================================================
|
|
309
|
+
|
|
310
|
+
if (require.main === module) {
|
|
311
|
+
const args = process.argv.slice(2);
|
|
312
|
+
|
|
313
|
+
// Parse --root and --json flags
|
|
314
|
+
let rootPath = process.cwd();
|
|
315
|
+
let jsonOutput = false;
|
|
316
|
+
|
|
317
|
+
for (let i = 0; i < args.length; i++) {
|
|
318
|
+
if (args[i] === '--root' && args[i + 1]) {
|
|
319
|
+
rootPath = args[i + 1];
|
|
320
|
+
i++;
|
|
321
|
+
} else if (args[i] === '--json') {
|
|
322
|
+
jsonOutput = true;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
const result = detectProjectIntent(rootPath);
|
|
328
|
+
|
|
329
|
+
if (jsonOutput) {
|
|
330
|
+
console.log(JSON.stringify(result, null, 2));
|
|
331
|
+
} else {
|
|
332
|
+
// Human-readable output
|
|
333
|
+
console.log('Project Intent Detection\n');
|
|
334
|
+
console.log('Suggested Intent:');
|
|
335
|
+
console.log(` base_profile: ${result.suggested.base_profile}`);
|
|
336
|
+
console.log(` iac: ${result.suggested.iac}`);
|
|
337
|
+
console.log(` deploy: ${result.suggested.deploy}`);
|
|
338
|
+
console.log(` sensitive: ${result.suggested.sensitive}`);
|
|
339
|
+
console.log(` oss: ${result.suggested.oss}`);
|
|
340
|
+
console.log(` monorepo: ${result.suggested.monorepo}\n`);
|
|
341
|
+
|
|
342
|
+
if (result.signals.length > 0) {
|
|
343
|
+
console.log('Signals:\n');
|
|
344
|
+
for (const signal of result.signals) {
|
|
345
|
+
console.log(` ${signal.dimension} (${signal.confidence} confidence):`);
|
|
346
|
+
for (const evidence of signal.evidence) {
|
|
347
|
+
console.log(` - ${evidence}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (result.needs_confirmation.length > 0) {
|
|
353
|
+
console.log(`\nNeeds Confirmation:\n ${result.needs_confirmation.join(', ')}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
} catch (err) {
|
|
357
|
+
console.error(`Error detecting project intent: ${err.message}`);
|
|
358
|
+
process.exit(1);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
module.exports = { detectProjectIntent };
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// bin/export-prism-constants.cjs
|
|
4
|
+
// Reads .planning/quorum-scoreboard.json and exports per-slot TP/UNAVAIL rates
|
|
5
|
+
// as PRISM const declarations to .planning/formal/prism/rates.const.
|
|
6
|
+
// Requirements: PRM-02, PRM-03
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
// ── Constants ────────────────────────────────────────────────────────────────
|
|
12
|
+
const MIN_ROUNDS = 30; // PRM-03: minimum rounds to use empirical rates
|
|
13
|
+
const TP_PRIOR = 0.85; // conservative TP prior when < 30 rounds
|
|
14
|
+
const UNAVAIL_PRIOR = 0.15; // conservative UNAVAIL prior when < 30 rounds
|
|
15
|
+
|
|
16
|
+
// Claude is excluded — Claude's self-vote is structural, not externally measured
|
|
17
|
+
const SLOTS = ['gemini', 'opencode', 'copilot', 'codex'];
|
|
18
|
+
|
|
19
|
+
// ── Pure Functions (exported for unit testing) ───────────────────────────────
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Compute per-slot TP and UNAVAIL rates from scoreboard rounds.
|
|
23
|
+
*
|
|
24
|
+
* @param {Array} rounds - scoreboard.rounds array
|
|
25
|
+
* @param {string} slot - model slot name (e.g. 'gemini')
|
|
26
|
+
* @param {number} minRounds - minimum rounds threshold (PRM-03)
|
|
27
|
+
* @param {number} tpPrior - conservative TP prior
|
|
28
|
+
* @param {number} unavailPrior - conservative UNAVAIL prior
|
|
29
|
+
* @returns {{ n: number, tpRate: number, unavailRate: number, usedPrior: boolean, warning: string|null }}
|
|
30
|
+
*/
|
|
31
|
+
function computeSlotRates(rounds, slot, minRounds, tpPrior, unavailPrior) {
|
|
32
|
+
// Filter to rounds where this slot has a classifiable vote.
|
|
33
|
+
// Exclude Mode A empty-string results and the UNAVAILABLE typo variant —
|
|
34
|
+
// they carry no binary signal and would inflate apparent unavailability.
|
|
35
|
+
const participated = rounds.filter(r => {
|
|
36
|
+
const v = r.votes && r.votes[slot];
|
|
37
|
+
return v !== undefined && v !== '' && v !== 'UNAVAILABLE';
|
|
38
|
+
});
|
|
39
|
+
const n = participated.length;
|
|
40
|
+
|
|
41
|
+
if (n < minRounds) {
|
|
42
|
+
return {
|
|
43
|
+
n,
|
|
44
|
+
tpRate: tpPrior,
|
|
45
|
+
unavailRate: unavailPrior,
|
|
46
|
+
usedPrior: true,
|
|
47
|
+
warning: `WARNING: slot '${slot}' has only ${n} rounds (fewer than ${minRounds} threshold) — using conservative priors (${tpPrior}/${unavailPrior})`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Count APPROVE (TP, TP+) and UNAVAIL votes
|
|
52
|
+
let approveCount = 0;
|
|
53
|
+
let unavailCount = 0;
|
|
54
|
+
for (const r of participated) {
|
|
55
|
+
const vote = r.votes[slot];
|
|
56
|
+
if (vote === 'TP' || vote === 'TP+') {
|
|
57
|
+
approveCount++;
|
|
58
|
+
} else if (vote === 'UNAVAIL') {
|
|
59
|
+
unavailCount++;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const tpRate = n > 0 ? approveCount / n : tpPrior;
|
|
64
|
+
const unavailRate = n > 0 ? unavailCount / n : unavailPrior;
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
n,
|
|
68
|
+
tpRate: parseFloat(tpRate.toFixed(4)),
|
|
69
|
+
unavailRate: parseFloat(unavailRate.toFixed(4)),
|
|
70
|
+
usedPrior: false,
|
|
71
|
+
warning: null,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Build PRISM const declaration lines for a slot.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} slot - model slot name
|
|
79
|
+
* @param {{ n: number, tpRate: number, unavailRate: number, usedPrior: boolean }} rateResult
|
|
80
|
+
* @returns {string[]} - array of string lines (without trailing newlines)
|
|
81
|
+
*/
|
|
82
|
+
function buildConstLines(slot, rateResult) {
|
|
83
|
+
const { n, tpRate, unavailRate, usedPrior } = rateResult;
|
|
84
|
+
const label = usedPrior
|
|
85
|
+
? `// ${slot}: n=${n} rounds — using priors (< ${MIN_ROUNDS} threshold)`
|
|
86
|
+
: `// ${slot}: n=${n} rounds (empirical)`;
|
|
87
|
+
return [
|
|
88
|
+
label,
|
|
89
|
+
`const double tp_${slot} = ${tpRate};`,
|
|
90
|
+
`const double unavail_${slot} = ${unavailRate};`,
|
|
91
|
+
'',
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── Export pure functions for unit testing ───────────────────────────────────
|
|
96
|
+
module.exports._pure = { computeSlotRates, buildConstLines };
|
|
97
|
+
|
|
98
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
99
|
+
// Guard against running main logic when required as a module (test imports)
|
|
100
|
+
if (require.main === module) {
|
|
101
|
+
let scoreboardPath;
|
|
102
|
+
try {
|
|
103
|
+
const pp = require('./planning-paths.cjs');
|
|
104
|
+
scoreboardPath = pp.resolveWithFallback(process.cwd(), 'quorum-scoreboard');
|
|
105
|
+
} catch (_) {
|
|
106
|
+
scoreboardPath = path.join(process.cwd(), '.planning', 'quorum-scoreboard.json');
|
|
107
|
+
}
|
|
108
|
+
const outputPath = path.join(process.cwd(), '.planning', 'formal', 'prism', 'rates.const');
|
|
109
|
+
|
|
110
|
+
// ── Locate scoreboard ──────────────────────────────────────────────────────
|
|
111
|
+
if (!fs.existsSync(scoreboardPath)) {
|
|
112
|
+
process.stderr.write(
|
|
113
|
+
'[export-prism-constants] No scoreboard file found at: ' + scoreboardPath + '\n' +
|
|
114
|
+
'[export-prism-constants] Run quorum rounds to populate the scoreboard first.\n'
|
|
115
|
+
);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let scoreboard;
|
|
120
|
+
try {
|
|
121
|
+
scoreboard = JSON.parse(fs.readFileSync(scoreboardPath, 'utf8'));
|
|
122
|
+
} catch (err) {
|
|
123
|
+
process.stderr.write(
|
|
124
|
+
'[export-prism-constants] Failed to parse scoreboard: ' + err.message + '\n'
|
|
125
|
+
);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const rounds = scoreboard.rounds || [];
|
|
130
|
+
|
|
131
|
+
// ── Compute rates per slot ─────────────────────────────────────────────────
|
|
132
|
+
const outputLines = [
|
|
133
|
+
'// PRISM rate constants — generated by bin/export-prism-constants.cjs',
|
|
134
|
+
'// Source: .planning/quorum-scoreboard.json (' + rounds.length + ' total rounds)',
|
|
135
|
+
'// Paste these lines into .planning/formal/prism/quorum.pm to override the default priors.',
|
|
136
|
+
'// Or use each as a -const CLI flag when invoking PRISM.',
|
|
137
|
+
'',
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
let hasWarnings = false;
|
|
141
|
+
|
|
142
|
+
for (const slot of SLOTS) {
|
|
143
|
+
const rateResult = computeSlotRates(rounds, slot, MIN_ROUNDS, TP_PRIOR, UNAVAIL_PRIOR);
|
|
144
|
+
|
|
145
|
+
if (rateResult.warning) {
|
|
146
|
+
process.stderr.write('[export-prism-constants] ' + rateResult.warning + '\n');
|
|
147
|
+
hasWarnings = true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const lines = buildConstLines(slot, rateResult);
|
|
151
|
+
outputLines.push(...lines);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── Write rates.const ─────────────────────────────────────────────────────
|
|
155
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
156
|
+
fs.writeFileSync(outputPath, outputLines.join('\n') + '\n');
|
|
157
|
+
|
|
158
|
+
process.stdout.write('[export-prism-constants] Written: ' + outputPath + '\n');
|
|
159
|
+
if (hasWarnings) {
|
|
160
|
+
process.stdout.write('[export-prism-constants] Note: some slots used conservative priors (< ' + MIN_ROUNDS + ' rounds)\n');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
process.exit(0);
|
|
164
|
+
}
|