@jaimevalasek/aioson 1.33.1 → 1.36.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/CHANGELOG.md +48 -0
- package/docs/en/1-understand/ecosystem-map.md +45 -29
- package/docs/en/1-understand/glossary.md +5 -5
- package/docs/en/1-understand/what-is-aioson.md +5 -5
- package/docs/en/2-start/existing-project.md +7 -7
- package/docs/en/2-start/first-project.md +32 -38
- package/docs/en/2-start/initial-decisions.md +18 -17
- package/docs/en/3-recipes/README.md +2 -2
- package/docs/en/3-recipes/continuity-between-sessions.md +2 -2
- package/docs/en/3-recipes/from-idea-to-prd-via-briefing.md +5 -2
- package/docs/en/3-recipes/full-feature-with-sheldon.md +327 -338
- package/docs/en/4-agents/README.md +28 -14
- package/docs/en/4-agents/discovery-design-doc.md +20 -8
- package/docs/en/4-agents/forge-run.md +3 -2
- package/docs/en/5-reference/cli-reference.md +51 -48
- package/docs/en/5-reference/executable-verification.md +10 -7
- package/docs/en/5-reference/parallel.md +2 -0
- package/docs/en/5-reference/qa-browser.md +2 -2
- package/docs/en/README.md +3 -3
- package/docs/pt/1-entender/glossario.md +4 -4
- package/docs/pt/1-entender/mapa-do-ecossistema.md +32 -21
- package/docs/pt/2-comecar/decisoes-iniciais.md +16 -16
- package/docs/pt/2-comecar/primeiro-projeto.md +1 -1
- package/docs/pt/3-receitas/README.md +1 -1
- package/docs/pt/3-receitas/app-saas-do-zero.md +35 -122
- package/docs/pt/3-receitas/feature-completa-com-sheldon.md +289 -338
- package/docs/pt/4-agentes/analyst.md +9 -5
- package/docs/pt/4-agentes/architect.md +9 -5
- package/docs/pt/4-agentes/dev.md +19 -7
- package/docs/pt/4-agentes/discovery-design-doc.md +25 -23
- package/docs/pt/4-agentes/orchestrator.md +164 -118
- package/docs/pt/4-agentes/pentester.md +5 -5
- package/docs/pt/4-agentes/pm.md +15 -7
- package/docs/pt/4-agentes/sheldon.md +166 -99
- package/docs/pt/4-agentes/ux-ui.md +7 -4
- package/docs/pt/5-referencia/agent-chain-continuity.md +14 -5
- package/docs/pt/5-referencia/sdd-automation-scripts.md +6 -6
- package/docs/pt/5-referencia/sdd-framework.md +53 -16
- package/docs/pt/5-referencia/sdd-planos-e-estrutura.md +27 -15
- package/docs/pt/README.md +4 -4
- package/docs/pt/agentes.md +48 -50
- package/package.json +2 -2
- package/src/artifact-kinds.js +110 -0
- package/src/cli.js +103 -80
- package/src/commands/agent-epilogue.js +251 -186
- package/src/commands/agents.js +104 -48
- package/src/commands/audit-code.js +344 -0
- package/src/commands/classify.js +389 -389
- package/src/commands/feature-close.js +43 -18
- package/src/commands/harness-check.js +259 -175
- package/src/commands/live.js +24 -0
- package/src/commands/review-feature.js +189 -0
- package/src/commands/runtime.js +81 -66
- package/src/commands/sync-agents-copy.js +115 -0
- package/src/commands/verification-plan.js +116 -0
- package/src/commands/verify-artifact.js +530 -0
- package/src/commands/workflow-execute.js +309 -309
- package/src/commands/workflow-next.js +139 -10
- package/src/commands/workflow-plan.js +5 -5
- package/src/constants.js +2 -0
- package/src/gateway-pointer-merge.js +7 -1
- package/src/handoff-contract.js +267 -172
- package/src/harness/contract-integrity-gate.js +172 -0
- package/src/harness/contract-integrity.js +111 -0
- package/src/harness/contract-schema.js +377 -332
- package/src/harness/detect-runtime-feature.js +90 -0
- package/src/harness/static-criteria.js +192 -0
- package/src/i18n/messages/en.js +8 -4
- package/src/i18n/messages/es.js +8 -4
- package/src/i18n/messages/fr.js +8 -4
- package/src/i18n/messages/pt-BR.js +8 -4
- package/src/install-wizard.js +8 -8
- package/src/installer.js +13 -6
- package/src/parser.js +6 -0
- package/src/preflight-engine.js +12 -12
- package/src/verification/policy-engine.js +95 -95
- package/src/verification-policy.js +398 -0
- package/src/version.js +52 -1
- package/template/.aioson/agents/architect.md +34 -5
- package/template/.aioson/agents/briefing-refiner.md +4 -1
- package/template/.aioson/agents/committer.md +2 -1
- package/template/.aioson/agents/copywriter.md +30 -21
- package/template/.aioson/agents/design-hybrid-forge.md +10 -1
- package/template/.aioson/agents/dev.md +27 -22
- package/template/.aioson/agents/discover.md +12 -3
- package/template/.aioson/agents/discovery-design-doc.md +7 -2
- package/template/.aioson/agents/genome.md +19 -10
- package/template/.aioson/agents/neo.md +30 -30
- package/template/.aioson/agents/orache.md +20 -11
- package/template/.aioson/agents/orchestrator.md +84 -7
- package/template/.aioson/agents/pm.md +8 -8
- package/template/.aioson/agents/profiler-enricher.md +20 -11
- package/template/.aioson/agents/profiler-forge.md +20 -11
- package/template/.aioson/agents/profiler-researcher.md +20 -11
- package/template/.aioson/agents/qa.md +91 -30
- package/template/.aioson/agents/setup.md +12 -1
- package/template/.aioson/agents/sheldon.md +94 -12
- package/template/.aioson/agents/site-forge.md +11 -2
- package/template/.aioson/agents/squad.md +20 -5
- package/template/.aioson/agents/ux-ui.md +1 -0
- package/template/.aioson/agents/validator.md +33 -1
- package/template/.aioson/config/verification.json +61 -0
- package/template/.aioson/config.md +13 -9
- package/template/.aioson/docs/LAYERS.md +2 -2
- package/template/.aioson/docs/autopilot-handoff.md +10 -10
- package/template/.aioson/docs/dev/execution-discipline.md +41 -0
- package/template/.aioson/docs/dev/phase-loop.md +47 -0
- package/template/.aioson/docs/presets/workflow.config.full-merged.json +15 -0
- package/template/.aioson/docs/presets/workflow.config.lean.json +15 -0
- package/template/.aioson/docs/product/prd-contract.md +12 -12
- package/template/.aioson/docs/prototype-contract.md +21 -4
- package/template/.aioson/docs/reference-identity.md +94 -0
- package/template/.aioson/docs/sheldon/harness-contract.md +155 -28
- package/template/.aioson/docs/verification-config.md +82 -0
- package/template/.aioson/docs/verify-artifact-gates.md +91 -0
- package/template/.aioson/docs/workflow-lean-lane.md +129 -0
- package/template/.aioson/skills/design/interface-design/references/intent-and-domain.md +2 -0
- package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +16 -14
- package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +2 -2
- package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +4 -4
- package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +15 -15
- package/template/.aioson/skills/process/prototype-forge/SKILL.md +6 -0
- package/template/.aioson/skills/process/reference-identity-extract/SKILL.md +164 -0
- package/template/.aioson/skills/static/multi-agent-patterns.md +5 -5
- package/template/CLAUDE.md +15 -11
|
@@ -22,10 +22,14 @@ const { emitDossierEvent } = require('../lib/dossier-telemetry');
|
|
|
22
22
|
const { appendScoutToFeatureDossier } = require('../dossier/scout-section');
|
|
23
23
|
const { emitSubTaskEvent } = require('../sub-task-telemetry');
|
|
24
24
|
const { loadConfig } = require('../sub-task-engine');
|
|
25
|
-
const { runDistillation, readFeatureClassification } = require('../learning-loop-engine');
|
|
26
|
-
const { openRuntimeDb } = require('../runtime-store');
|
|
27
|
-
const { runNotify } = require('./notify');
|
|
28
|
-
const { splitCurrentState, buildArchiveContent, parseActiveSlugs } = require('../current-state-trim');
|
|
25
|
+
const { runDistillation, readFeatureClassification } = require('../learning-loop-engine');
|
|
26
|
+
const { openRuntimeDb } = require('../runtime-store');
|
|
27
|
+
const { runNotify } = require('./notify');
|
|
28
|
+
const { splitCurrentState, buildArchiveContent, parseActiveSlugs } = require('../current-state-trim');
|
|
29
|
+
const {
|
|
30
|
+
evaluateContractIntegrityGate,
|
|
31
|
+
formatContractIntegrityGateError
|
|
32
|
+
} = require('../harness/contract-integrity-gate');
|
|
29
33
|
|
|
30
34
|
// P0 agent-loading-contract: a feature closing is the natural cadence to roll
|
|
31
35
|
// aged-out current-state.md entries into the cold archive. Conservative window
|
|
@@ -322,18 +326,40 @@ async function runFeatureClose({ args, options = {}, logger }) {
|
|
|
322
326
|
const updates = [];
|
|
323
327
|
|
|
324
328
|
// 0a. Harness Done Gate (AC-HD-11 refined)
|
|
325
|
-
// Only enforced on PASS — FAIL means QA already rejected and we want the
|
|
326
|
-
// closure to record that.
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
+
// Only enforced on PASS — FAIL means QA already rejected and we want the
|
|
330
|
+
// closure to record that. Runtime features the CLI can detect (prototype or
|
|
331
|
+
// migration/Prisma evidence) must have a valid contract even on MICRO/SMALL;
|
|
332
|
+
// non-runtime features without a contract keep the historical lightweight path.
|
|
329
333
|
if (verdict === 'PASS') {
|
|
330
334
|
const planDir = path.join(targetDir, '.aioson', 'plans', slug);
|
|
331
|
-
const contractPath = path.join(planDir, 'harness-contract.json');
|
|
332
|
-
const progressPath = path.join(planDir, 'progress.json');
|
|
333
|
-
const contractContent = await readFileSafe(contractPath);
|
|
334
|
-
const progressContent = await readFileSafe(progressPath);
|
|
335
|
-
|
|
336
|
-
|
|
335
|
+
const contractPath = path.join(planDir, 'harness-contract.json');
|
|
336
|
+
const progressPath = path.join(planDir, 'progress.json');
|
|
337
|
+
const contractContent = await readFileSafe(contractPath);
|
|
338
|
+
const progressContent = await readFileSafe(progressPath);
|
|
339
|
+
const force = options.force === true;
|
|
340
|
+
|
|
341
|
+
const integrityGate = await evaluateContractIntegrityGate(targetDir, slug, {
|
|
342
|
+
runChecks: Boolean(contractContent)
|
|
343
|
+
});
|
|
344
|
+
if (!integrityGate.ok) {
|
|
345
|
+
if (!force) {
|
|
346
|
+
const errMsg = formatContractIntegrityGateError(integrityGate);
|
|
347
|
+
if (options.json) {
|
|
348
|
+
return {
|
|
349
|
+
ok: false,
|
|
350
|
+
reason: 'harness_contract_gate_blocked',
|
|
351
|
+
feature: slug,
|
|
352
|
+
error: errMsg,
|
|
353
|
+
errors: integrityGate.errors
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
logger.log(errMsg);
|
|
357
|
+
return { ok: false, reason: 'harness_contract_gate_blocked' };
|
|
358
|
+
}
|
|
359
|
+
updates.push('harness contract gate: BYPASSED via --force');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// REQ-13 (loop-guardrails): tema `publish` é gate de COMANDO — intercepta
|
|
337
363
|
// o feature:close quando o contrato ativo o exige e não há gate publish
|
|
338
364
|
// aprovado. Nunca detectado por diff. `--force` NÃO bypassa: a aprovação
|
|
339
365
|
// humana é o propósito do gate (decisão registrada no spec da feature).
|
|
@@ -372,10 +398,9 @@ async function runFeatureClose({ args, options = {}, logger }) {
|
|
|
372
398
|
}
|
|
373
399
|
} catch { /* contrato ilegível — o done gate abaixo lida com o estado */ }
|
|
374
400
|
}
|
|
375
|
-
|
|
376
|
-
if (contractContent && progressContent) {
|
|
377
|
-
|
|
378
|
-
let progress = null;
|
|
401
|
+
|
|
402
|
+
if (contractContent && progressContent) {
|
|
403
|
+
let progress = null;
|
|
379
404
|
try {
|
|
380
405
|
progress = JSON.parse(progressContent);
|
|
381
406
|
} catch (err) {
|
|
@@ -1,175 +1,259 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* aioson harness:check [path] --slug=<slug> [--criteria=C1,C2] [--timeout=<ms>]
|
|
5
|
-
* [--json] — runner standalone e determinístico de criteria[].verification.
|
|
6
|
-
*
|
|
7
|
-
* Executa os checks executáveis do contrato FORA do self:loop, para consumo
|
|
8
|
-
* pelo @validator (verificação binária antes de julgamento LLM), pelo @dev
|
|
9
|
-
* (feedback rápido pré-done) ou por CI. Reusa runCriteria/executeInSandbox —
|
|
10
|
-
* NÃO cria runner novo.
|
|
11
|
-
*
|
|
12
|
-
* Read-only sobre progress.json: quem muda estado do circuito continua sendo
|
|
13
|
-
* o ciclo harness:validate/apply-validation. Persiste o resultado em
|
|
14
|
-
* `last-check-output.json` no plan dir (espelha a convenção
|
|
15
|
-
* last-validator-output.json) e emite telemetria criteria_check_failed
|
|
16
|
-
* (best-effort, nunca quebra).
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
const fs = require('node:fs');
|
|
20
|
-
const path = require('node:path');
|
|
21
|
-
|
|
22
|
-
const { validateContract, resolveContract } = require('../harness/contract-schema');
|
|
23
|
-
const { runCriteria, DEFAULT_CHECK_TIMEOUT_MS } = require('../harness/criteria-runner');
|
|
24
|
-
const {
|
|
25
|
-
const {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* aioson harness:check [path] --slug=<slug> [--criteria=C1,C2] [--timeout=<ms>]
|
|
5
|
+
* [--json] — runner standalone e determinístico de criteria[].verification.
|
|
6
|
+
*
|
|
7
|
+
* Executa os checks executáveis do contrato FORA do self:loop, para consumo
|
|
8
|
+
* pelo @validator (verificação binária antes de julgamento LLM), pelo @dev
|
|
9
|
+
* (feedback rápido pré-done) ou por CI. Reusa runCriteria/executeInSandbox —
|
|
10
|
+
* NÃO cria runner novo.
|
|
11
|
+
*
|
|
12
|
+
* Read-only sobre progress.json: quem muda estado do circuito continua sendo
|
|
13
|
+
* o ciclo harness:validate/apply-validation. Persiste o resultado em
|
|
14
|
+
* `last-check-output.json` no plan dir (espelha a convenção
|
|
15
|
+
* last-validator-output.json) e emite telemetria criteria_check_failed
|
|
16
|
+
* (best-effort, nunca quebra).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('node:fs');
|
|
20
|
+
const path = require('node:path');
|
|
21
|
+
|
|
22
|
+
const { validateContract, resolveContract } = require('../harness/contract-schema');
|
|
23
|
+
const { runCriteria, DEFAULT_CHECK_TIMEOUT_MS } = require('../harness/criteria-runner');
|
|
24
|
+
const { evaluateStaticCriteria, isStaticCriterion } = require('../harness/static-criteria');
|
|
25
|
+
const { emitGuardEvent } = require('../harness/guard-events');
|
|
26
|
+
const { findActiveContract } = require('../harness/active-contract');
|
|
27
|
+
const { checkContractIntegrity } = require('../harness/contract-integrity');
|
|
28
|
+
const { detectRuntimeFeature, gitChangedFiles } = require('../harness/detect-runtime-feature');
|
|
29
|
+
|
|
30
|
+
function readProgressSignals(planDir) {
|
|
31
|
+
// Same progress fields the contract-integrity gate consults, so standalone
|
|
32
|
+
// `harness:check` detects the identical migration surface (completed_steps +
|
|
33
|
+
// changed_files + touched_files) the workflow finalize / feature:close gate does.
|
|
34
|
+
try {
|
|
35
|
+
const progressPath = path.join(planDir, 'progress.json');
|
|
36
|
+
if (!fs.existsSync(progressPath)) return [];
|
|
37
|
+
const progress = JSON.parse(fs.readFileSync(progressPath, 'utf8'));
|
|
38
|
+
return [progress.completed_steps, progress.changed_files, progress.touched_files]
|
|
39
|
+
.flatMap((value) => (Array.isArray(value) ? value : []));
|
|
40
|
+
} catch {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function resolveSlug(targetDir, options) {
|
|
46
|
+
const explicit = String(options.slug || '').trim();
|
|
47
|
+
if (explicit) return explicit;
|
|
48
|
+
try {
|
|
49
|
+
const active = findActiveContract(targetDir);
|
|
50
|
+
return active ? active.slug : '';
|
|
51
|
+
} catch {
|
|
52
|
+
return '';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function runHarnessCheck({ args, options = {}, logger, t }) {
|
|
57
|
+
const targetDir = path.resolve(process.cwd(), args?.[0] || '.');
|
|
58
|
+
const slug = resolveSlug(targetDir, options);
|
|
59
|
+
|
|
60
|
+
if (!slug) {
|
|
61
|
+
logger.error(t('errors.missing_slug') || 'Error: --slug is required');
|
|
62
|
+
return { ok: false, error: 'missing_slug' };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const planDir = path.join(targetDir, '.aioson', 'plans', slug);
|
|
66
|
+
const contractPath = path.join(planDir, 'harness-contract.json');
|
|
67
|
+
|
|
68
|
+
if (!fs.existsSync(contractPath)) {
|
|
69
|
+
logger.error(t('harness.contract_not_found', { slug }) || `Contract not found for slug: ${slug}`);
|
|
70
|
+
return { ok: false, error: 'contract_not_found', slug };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let contract;
|
|
74
|
+
try {
|
|
75
|
+
contract = JSON.parse(fs.readFileSync(contractPath, 'utf8'));
|
|
76
|
+
} catch (err) {
|
|
77
|
+
logger.error(`Invalid JSON in contract: ${err.message}`);
|
|
78
|
+
return { ok: false, error: 'invalid_json', slug, detail: err.message };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const schemaResult = validateContract(contract);
|
|
82
|
+
if (!schemaResult.ok) {
|
|
83
|
+
const first = schemaResult.errors[0];
|
|
84
|
+
logger.error(`Contract schema invalid: ${first.field} — ${first.reason}`);
|
|
85
|
+
await emitGuardEvent(targetDir, {
|
|
86
|
+
eventType: 'contract_invalid',
|
|
87
|
+
agent: 'harness-check',
|
|
88
|
+
message: `${first.field}: ${first.reason}`,
|
|
89
|
+
payload: { slug }
|
|
90
|
+
});
|
|
91
|
+
return { ok: false, error: 'contract_schema_invalid', slug, errors: schemaResult.errors };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const resolved = resolveContract(contract);
|
|
95
|
+
|
|
96
|
+
// ── Contract-integrity precheck (§2c, deterministic core of @validator Step 0) ──
|
|
97
|
+
// A runtime feature whose contract has no RG-* criterion, or that pads binary
|
|
98
|
+
// criteria with duplicate verification commands, cannot prove the app runs.
|
|
99
|
+
// Detection only fires on signals the framework locates reliably (prototype
|
|
100
|
+
// manifest, migration steps); the Play has_api trigger stays with @validator.
|
|
101
|
+
const runtime = detectRuntimeFeature(targetDir, slug, {
|
|
102
|
+
completedSteps: readProgressSignals(planDir),
|
|
103
|
+
changedFiles: gitChangedFiles(targetDir)
|
|
104
|
+
});
|
|
105
|
+
const integrity = checkContractIntegrity(contract, { isRuntimeFeature: runtime.isRuntimeFeature });
|
|
106
|
+
for (const err of integrity.errors) {
|
|
107
|
+
await emitGuardEvent(targetDir, {
|
|
108
|
+
eventType: 'contract_integrity_failed',
|
|
109
|
+
agent: 'harness-check',
|
|
110
|
+
message: `${err.code}: ${err.message}`,
|
|
111
|
+
payload: { slug, code: err.code, signals: runtime.signals }
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let criteria = resolved.criteria;
|
|
116
|
+
if (options.criteria) {
|
|
117
|
+
const wanted = new Set(
|
|
118
|
+
String(options.criteria).split(',').map((id) => id.trim()).filter(Boolean)
|
|
119
|
+
);
|
|
120
|
+
criteria = criteria.filter((c) => c && wanted.has(c.id));
|
|
121
|
+
const found = new Set(criteria.map((c) => c.id));
|
|
122
|
+
const missing = [...wanted].filter((id) => !found.has(id));
|
|
123
|
+
if (missing.length) {
|
|
124
|
+
logger.error(t('harness.check_unknown_criteria', { ids: missing.join(', ') }) || `Unknown criteria ids: ${missing.join(', ')}`);
|
|
125
|
+
return { ok: false, error: 'unknown_criteria', slug, missing };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const timeoutMs = Number.isInteger(Number(options.timeout)) && Number(options.timeout) > 0
|
|
130
|
+
? Number(options.timeout)
|
|
131
|
+
: DEFAULT_CHECK_TIMEOUT_MS;
|
|
132
|
+
|
|
133
|
+
const executable = criteria.filter(
|
|
134
|
+
(c) => c && typeof c.verification === 'string' && c.verification.trim()
|
|
135
|
+
);
|
|
136
|
+
// Static (SG-*) criteria are deterministically checkable too — they are NOT
|
|
137
|
+
// "skipped / @validator-judged", so exclude them from that count and from the
|
|
138
|
+
// strict-mode verification-debt checks below.
|
|
139
|
+
const staticCriteria = criteria.filter((c) => isStaticCriterion(c));
|
|
140
|
+
const skipped = criteria.length - executable.length - staticCriteria.length;
|
|
141
|
+
const strict = Boolean(options.strict);
|
|
142
|
+
const binaryWithoutVerification = criteria.filter(
|
|
143
|
+
(c) => c && c.binary === true && !(typeof c.verification === 'string' && c.verification.trim()) && !isStaticCriterion(c)
|
|
144
|
+
);
|
|
145
|
+
const strictErrors = [];
|
|
146
|
+
if (strict && criteria.length > 0 && executable.length === 0 && staticCriteria.length === 0) {
|
|
147
|
+
strictErrors.push('strict mode requires at least one executable or static (SG-*) verification criterion');
|
|
148
|
+
}
|
|
149
|
+
if (strict && binaryWithoutVerification.length > 0) {
|
|
150
|
+
strictErrors.push(`strict mode requires verification for binary criteria: ${binaryWithoutVerification.map((c) => c.id).join(', ')}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const checks = await runCriteria({ criteria, cwd: targetDir, timeoutMs });
|
|
154
|
+
const failed = checks.filter((c) => !c.ok);
|
|
155
|
+
|
|
156
|
+
// SG-* static criteria — build-independent (fs + RegExp + parse), evaluated on
|
|
157
|
+
// every run (cheap). They gate the report just like executable criteria.
|
|
158
|
+
const staticResult = evaluateStaticCriteria({ criteria, cwd: targetDir });
|
|
159
|
+
const staticFailed = staticResult.checks.filter((c) => !c.ok);
|
|
160
|
+
|
|
161
|
+
for (const check of failed) {
|
|
162
|
+
await emitGuardEvent(targetDir, {
|
|
163
|
+
eventType: 'criteria_check_failed',
|
|
164
|
+
agent: 'harness-check',
|
|
165
|
+
message: `${check.id}: exit ${check.exitCode}${check.timedOut ? ' (timeout)' : ''}`,
|
|
166
|
+
payload: { slug, criterion_id: check.id, exit_code: check.exitCode, signature: check.signature }
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
for (const check of staticFailed) {
|
|
170
|
+
await emitGuardEvent(targetDir, {
|
|
171
|
+
eventType: 'criteria_check_failed',
|
|
172
|
+
agent: 'harness-check',
|
|
173
|
+
message: `${check.id}: ${check.detail}`,
|
|
174
|
+
payload: { slug, criterion_id: check.id, kind: 'static' }
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const report = {
|
|
179
|
+
ok: failed.length === 0 && staticFailed.length === 0 && strictErrors.length === 0 && integrity.ok,
|
|
180
|
+
slug,
|
|
181
|
+
checked_at: new Date().toISOString(),
|
|
182
|
+
strict,
|
|
183
|
+
criteria_total: criteria.length,
|
|
184
|
+
executable_total: executable.length,
|
|
185
|
+
passed: checks.length - failed.length,
|
|
186
|
+
failed: failed.length,
|
|
187
|
+
skipped_no_verification: skipped,
|
|
188
|
+
static_total: staticResult.total,
|
|
189
|
+
static_passed: staticResult.total - staticFailed.length,
|
|
190
|
+
static_failed: staticFailed.length,
|
|
191
|
+
strict_errors: strictErrors,
|
|
192
|
+
integrity: {
|
|
193
|
+
ok: integrity.ok,
|
|
194
|
+
is_runtime_feature: runtime.isRuntimeFeature,
|
|
195
|
+
runtime_signals: runtime.signals,
|
|
196
|
+
errors: integrity.errors,
|
|
197
|
+
warnings: integrity.warnings
|
|
198
|
+
},
|
|
199
|
+
checks,
|
|
200
|
+
static_checks: staticResult.checks
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
if (!fs.existsSync(planDir)) fs.mkdirSync(planDir, { recursive: true });
|
|
205
|
+
fs.writeFileSync(
|
|
206
|
+
path.join(planDir, 'last-check-output.json'),
|
|
207
|
+
JSON.stringify(report, null, 2),
|
|
208
|
+
'utf8'
|
|
209
|
+
);
|
|
210
|
+
} catch (err) {
|
|
211
|
+
// Persistência é conveniência, não gate — reporta sem falhar o run.
|
|
212
|
+
logger.error(`Could not persist last-check-output.json: ${err.message}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (options.json) {
|
|
216
|
+
logger.log(JSON.stringify(report, null, 2));
|
|
217
|
+
return report;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
logger.log(t('harness.check_header', { slug }) || `Harness check — ${slug}`);
|
|
221
|
+
for (const err of integrity.errors) {
|
|
222
|
+
logger.log(` ✗ contract-integrity (${err.code}): ${err.message}`);
|
|
223
|
+
}
|
|
224
|
+
for (const warn of integrity.warnings) {
|
|
225
|
+
logger.log(` ⚠ contract-integrity (${warn.code}): ${warn.message}`);
|
|
226
|
+
}
|
|
227
|
+
for (const error of strictErrors) {
|
|
228
|
+
logger.log(` ✗ ${error}`);
|
|
229
|
+
}
|
|
230
|
+
// SG-* static criteria render on every run — they are build-independent.
|
|
231
|
+
for (const check of staticResult.checks) {
|
|
232
|
+
const mark = check.ok ? '✓' : '✗';
|
|
233
|
+
const extra = check.ok ? ` [${check.files.join(', ')}]` : ` — ${check.detail}`;
|
|
234
|
+
logger.log(` ${mark} ${check.id} (static)${extra}`);
|
|
235
|
+
}
|
|
236
|
+
if (executable.length === 0) {
|
|
237
|
+
if (staticResult.total > 0) {
|
|
238
|
+
logger.log(` Static: ${report.static_passed}/${staticResult.total} SG-* passed${skipped > 0 ? ` (${skipped} without verification — judged by @validator)` : ''}.`);
|
|
239
|
+
} else {
|
|
240
|
+
logger.log(t('harness.check_no_executable', { total: criteria.length }) || ` No criteria with verification commands (${criteria.length} criteria total). @validator judges them all.`);
|
|
241
|
+
}
|
|
242
|
+
return report;
|
|
243
|
+
}
|
|
244
|
+
for (const check of checks) {
|
|
245
|
+
const mark = check.ok ? '✓' : '✗';
|
|
246
|
+
const extra = check.ok ? '' : ` (exit ${check.exitCode}${check.timedOut ? ', timeout' : ''})`;
|
|
247
|
+
logger.log(` ${mark} ${check.id} — ${check.command}${extra} [${check.durationMs}ms]`);
|
|
248
|
+
}
|
|
249
|
+
logger.log(
|
|
250
|
+
t('harness.check_summary', { passed: report.passed, executable: executable.length, skipped }) ||
|
|
251
|
+
` Checks: ${report.passed}/${executable.length} passed (${skipped} without verification — judged by @validator)`
|
|
252
|
+
);
|
|
253
|
+
if (staticResult.total > 0) {
|
|
254
|
+
logger.log(` Static: ${report.static_passed}/${staticResult.total} SG-* passed.`);
|
|
255
|
+
}
|
|
256
|
+
return report;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
module.exports = { runHarnessCheck };
|
package/src/commands/live.js
CHANGED
|
@@ -262,6 +262,24 @@ function spawnExecutable(binaryPath) {
|
|
|
262
262
|
return process.platform === 'win32' ? `"${binaryPath}"` : binaryPath;
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
+
function isWindowsPidAlive(pid) {
|
|
266
|
+
// process.kill(pid, 0) is not authoritative on Windows: it throws EPERM both
|
|
267
|
+
// for "process exists but no access" AND (commonly) for PIDs that are already
|
|
268
|
+
// gone. Probe the real process table so a dead PID reconciles instead of
|
|
269
|
+
// looking 'unknown' forever (which blocks live:start with "session already
|
|
270
|
+
// active"). Returns true/false when known, null when the probe itself failed.
|
|
271
|
+
try {
|
|
272
|
+
const out = require('node:child_process').execFileSync(
|
|
273
|
+
'tasklist',
|
|
274
|
+
['/FI', `PID eq ${pid}`, '/NH', '/FO', 'CSV'],
|
|
275
|
+
{ encoding: 'utf8', windowsHide: true, timeout: 3000 }
|
|
276
|
+
);
|
|
277
|
+
return out.includes(`"${pid}"`);
|
|
278
|
+
} catch {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
265
283
|
function detectProcessState(pid) {
|
|
266
284
|
if (!pid) return 'not_tracked';
|
|
267
285
|
try {
|
|
@@ -271,6 +289,12 @@ function detectProcessState(pid) {
|
|
|
271
289
|
if (error && error.code === 'ESRCH') {
|
|
272
290
|
return 'dead';
|
|
273
291
|
}
|
|
292
|
+
if (process.platform === 'win32') {
|
|
293
|
+
const alive = isWindowsPidAlive(Number(pid));
|
|
294
|
+
if (alive === true) return 'alive';
|
|
295
|
+
if (alive === false) return 'dead';
|
|
296
|
+
// probe inconclusive → fall through to 'unknown'
|
|
297
|
+
}
|
|
274
298
|
return 'unknown';
|
|
275
299
|
}
|
|
276
300
|
}
|