@jaimevalasek/aioson 1.30.2 → 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 +75 -0
- package/README.md +19 -6
- 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/comandos-cli.md +1 -1
- package/docs/pt/5-referencia/harness-retro.md +2 -1
- 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 +82 -42
- 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 +75 -13
- package/src/commands/feature-close.js +43 -18
- package/src/commands/harness-check.js +259 -175
- package/src/commands/harness-retro-promote.js +387 -0
- package/src/commands/live.js +24 -0
- package/src/commands/prototype-check.js +163 -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/verify-implementation.js +428 -0
- package/src/commands/workflow-execute.js +309 -309
- package/src/commands/workflow-next.js +361 -19
- package/src/commands/workflow-plan.js +5 -5
- package/src/constants.js +4 -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/lib/retro/retro-render.js +10 -1
- package/src/lib/retro/retro-sources.js +45 -27
- package/src/lib/retro/verification-reports.js +230 -0
- package/src/parser.js +6 -0
- package/src/preflight-engine.js +12 -12
- package/src/runtime-store.js +13 -9
- package/src/verification/evidence-bundle.js +251 -0
- package/src/verification/ledger-store.js +221 -0
- package/src/verification/path-policy.js +74 -0
- package/src/verification/policy-engine.js +95 -0
- package/src/verification/prompt-package.js +314 -0
- package/src/verification/redaction.js +77 -0
- package/src/verification/report-parser.js +132 -0
- package/src/verification/report-store.js +97 -0
- package/src/verification/result.js +16 -0
- package/src/verification/runners/index.js +319 -0
- package/src/verification/runtime-telemetry.js +144 -0
- package/src/verification/schema.js +276 -0
- package/src/verification/source-discovery.js +153 -0
- package/src/verification-policy.js +398 -0
- package/src/version.js +52 -1
- package/template/.aioson/agents/analyst.md +9 -6
- package/template/.aioson/agents/architect.md +34 -5
- package/template/.aioson/agents/briefing-refiner.md +25 -0
- package/template/.aioson/agents/briefing.md +69 -12
- package/template/.aioson/agents/committer.md +2 -1
- package/template/.aioson/agents/copywriter.md +30 -21
- package/template/.aioson/agents/design-hybrid-forge.md +28 -15
- package/template/.aioson/agents/dev.md +28 -10
- package/template/.aioson/agents/deyvin.md +3 -2
- 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/product.md +15 -11
- 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 +87 -17
- package/template/.aioson/agents/scope-check.md +19 -5
- package/template/.aioson/agents/setup.md +12 -1
- package/template/.aioson/agents/sheldon.md +92 -9
- package/template/.aioson/agents/site-forge.md +11 -2
- package/template/.aioson/agents/squad.md +20 -5
- package/template/.aioson/agents/ux-ui.md +4 -2
- 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/feature-expansion-taxonomy.md +31 -3
- 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 +98 -0
- 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/briefing-expansion-scout/SKILL.md +25 -3
- package/template/.aioson/skills/process/design-hybrid-forge/SKILL.md +5 -3
- package/template/.aioson/skills/process/design-hybrid-forge/references/external-source-ingestion.md +89 -0
- package/template/.aioson/skills/process/product-scope-expansion/SKILL.md +29 -2
- package/template/.aioson/skills/process/prototype-forge/SKILL.md +98 -0
- package/template/.aioson/skills/process/reference-identity-extract/SKILL.md +164 -0
- package/template/.aioson/skills/process/sheldon-expansion-audit/SKILL.md +23 -2
- package/template/.aioson/skills/static/multi-agent-patterns.md +5 -5
- package/template/CLAUDE.md +15 -11
|
@@ -1,332 +1,377 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Validação de schema do harness-contract.json (loop-guardrails REQ-1).
|
|
5
|
-
*
|
|
6
|
-
* Responsabilidade distinta de `harness:validate` (que valida a IMPLEMENTAÇÃO
|
|
7
|
-
* contra criteria via @validator). Aqui valida-se o CONTRATO em si, no
|
|
8
|
-
* preflight do `self:loop` — um typo em `allowed_files` não pode desligar o
|
|
9
|
-
* scope guard silenciosamente. Mensagens usam "contract schema invalid",
|
|
10
|
-
* nunca "validation verdict".
|
|
11
|
-
*
|
|
12
|
-
* Retrocompat (REQ-11 / EC-12): contratos antigos (feature/contract_mode/
|
|
13
|
-
* governor/criteria) passam; campos novos são todos opcionais.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const { validateGlobPattern } = require('./glob-match');
|
|
17
|
-
|
|
18
|
-
/** REQ-4 — defaults proibidos, sempre aplicados e não-removíveis (SEC-SBD-05). */
|
|
19
|
-
const DEFAULT_FORBIDDEN_GLOBS = Object.freeze([
|
|
20
|
-
'.env*',
|
|
21
|
-
'*.pem',
|
|
22
|
-
'*.key',
|
|
23
|
-
'secrets/**',
|
|
24
|
-
'.git/**',
|
|
25
|
-
'node_modules/**',
|
|
26
|
-
'package-lock.json',
|
|
27
|
-
'yarn.lock',
|
|
28
|
-
'pnpm-lock.yaml',
|
|
29
|
-
'npm-shrinkwrap.json',
|
|
30
|
-
'bun.lockb'
|
|
31
|
-
]);
|
|
32
|
-
|
|
33
|
-
const HUMAN_GATE_THEMES = Object.freeze([
|
|
34
|
-
'payment_logic_change',
|
|
35
|
-
'auth_permission_change',
|
|
36
|
-
'database_destructive_change',
|
|
37
|
-
'publish'
|
|
38
|
-
]);
|
|
39
|
-
|
|
40
|
-
/** Mapa default tema→globs (requirements §2.1); override via human_gate.themes[].paths. */
|
|
41
|
-
const DEFAULT_THEME_PATHS = Object.freeze({
|
|
42
|
-
payment_logic_change: Object.freeze(['**/billing/**', '**/payment/**']),
|
|
43
|
-
auth_permission_change: Object.freeze(['**/auth/**']),
|
|
44
|
-
database_destructive_change: Object.freeze(['**/migrations/**']),
|
|
45
|
-
publish: Object.freeze([]) // gate de comando (REQ-13), nunca diff
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Presets do contract_mode (REQ-19). Preenchem apenas valores do governor
|
|
50
|
-
* NÃO definidos explicitamente no contrato; valor explícito sempre vence.
|
|
51
|
-
* `BALANCED` mantém o comportamento atual (nenhum preenchimento).
|
|
52
|
-
*/
|
|
53
|
-
const CONTRACT_PRESETS = Object.freeze({
|
|
54
|
-
safe: Object.freeze({
|
|
55
|
-
max_steps: 10,
|
|
56
|
-
error_streak_limit: 3,
|
|
57
|
-
cost_ceiling_tokens: 200000,
|
|
58
|
-
max_runtime_minutes: 30,
|
|
59
|
-
max_changed_files: 20,
|
|
60
|
-
max_diff_lines: 1500
|
|
61
|
-
}),
|
|
62
|
-
builder: Object.freeze({
|
|
63
|
-
max_steps: 30,
|
|
64
|
-
error_streak_limit: 5,
|
|
65
|
-
cost_ceiling_tokens: 1000000,
|
|
66
|
-
max_runtime_minutes: 120,
|
|
67
|
-
max_changed_files: 60,
|
|
68
|
-
max_diff_lines: 6000
|
|
69
|
-
}),
|
|
70
|
-
autopilot: Object.freeze({
|
|
71
|
-
max_steps: 50,
|
|
72
|
-
error_streak_limit: 8,
|
|
73
|
-
cost_ceiling_tokens: 3000000,
|
|
74
|
-
max_runtime_minutes: 360,
|
|
75
|
-
max_changed_files: null,
|
|
76
|
-
max_diff_lines: null
|
|
77
|
-
})
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
const KNOWN_TOP_FIELDS = new Set([
|
|
81
|
-
'feature', 'contract_mode', 'governor', 'criteria',
|
|
82
|
-
'allowed_files', 'forbidden_files', 'human_gate'
|
|
83
|
-
]);
|
|
84
|
-
const KNOWN_GOVERNOR_FIELDS = new Set([
|
|
85
|
-
'max_steps', 'error_streak_limit', 'cost_ceiling_tokens',
|
|
86
|
-
'max_runtime_minutes', 'max_changed_files', 'max_diff_lines'
|
|
87
|
-
]);
|
|
88
|
-
const KNOWN_HUMAN_GATE_FIELDS = new Set(['required_for', 'themes']);
|
|
89
|
-
const KNOWN_THEME_FIELDS = new Set(['name', 'paths']);
|
|
90
|
-
const KNOWN_CRITERIA_FIELDS = new Set([
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
function
|
|
99
|
-
return
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function
|
|
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
|
-
if (contract.
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (contract.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validação de schema do harness-contract.json (loop-guardrails REQ-1).
|
|
5
|
+
*
|
|
6
|
+
* Responsabilidade distinta de `harness:validate` (que valida a IMPLEMENTAÇÃO
|
|
7
|
+
* contra criteria via @validator). Aqui valida-se o CONTRATO em si, no
|
|
8
|
+
* preflight do `self:loop` — um typo em `allowed_files` não pode desligar o
|
|
9
|
+
* scope guard silenciosamente. Mensagens usam "contract schema invalid",
|
|
10
|
+
* nunca "validation verdict".
|
|
11
|
+
*
|
|
12
|
+
* Retrocompat (REQ-11 / EC-12): contratos antigos (feature/contract_mode/
|
|
13
|
+
* governor/criteria) passam; campos novos são todos opcionais.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const { validateGlobPattern } = require('./glob-match');
|
|
17
|
+
|
|
18
|
+
/** REQ-4 — defaults proibidos, sempre aplicados e não-removíveis (SEC-SBD-05). */
|
|
19
|
+
const DEFAULT_FORBIDDEN_GLOBS = Object.freeze([
|
|
20
|
+
'.env*',
|
|
21
|
+
'*.pem',
|
|
22
|
+
'*.key',
|
|
23
|
+
'secrets/**',
|
|
24
|
+
'.git/**',
|
|
25
|
+
'node_modules/**',
|
|
26
|
+
'package-lock.json',
|
|
27
|
+
'yarn.lock',
|
|
28
|
+
'pnpm-lock.yaml',
|
|
29
|
+
'npm-shrinkwrap.json',
|
|
30
|
+
'bun.lockb'
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
const HUMAN_GATE_THEMES = Object.freeze([
|
|
34
|
+
'payment_logic_change',
|
|
35
|
+
'auth_permission_change',
|
|
36
|
+
'database_destructive_change',
|
|
37
|
+
'publish'
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
/** Mapa default tema→globs (requirements §2.1); override via human_gate.themes[].paths. */
|
|
41
|
+
const DEFAULT_THEME_PATHS = Object.freeze({
|
|
42
|
+
payment_logic_change: Object.freeze(['**/billing/**', '**/payment/**']),
|
|
43
|
+
auth_permission_change: Object.freeze(['**/auth/**']),
|
|
44
|
+
database_destructive_change: Object.freeze(['**/migrations/**']),
|
|
45
|
+
publish: Object.freeze([]) // gate de comando (REQ-13), nunca diff
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Presets do contract_mode (REQ-19). Preenchem apenas valores do governor
|
|
50
|
+
* NÃO definidos explicitamente no contrato; valor explícito sempre vence.
|
|
51
|
+
* `BALANCED` mantém o comportamento atual (nenhum preenchimento).
|
|
52
|
+
*/
|
|
53
|
+
const CONTRACT_PRESETS = Object.freeze({
|
|
54
|
+
safe: Object.freeze({
|
|
55
|
+
max_steps: 10,
|
|
56
|
+
error_streak_limit: 3,
|
|
57
|
+
cost_ceiling_tokens: 200000,
|
|
58
|
+
max_runtime_minutes: 30,
|
|
59
|
+
max_changed_files: 20,
|
|
60
|
+
max_diff_lines: 1500
|
|
61
|
+
}),
|
|
62
|
+
builder: Object.freeze({
|
|
63
|
+
max_steps: 30,
|
|
64
|
+
error_streak_limit: 5,
|
|
65
|
+
cost_ceiling_tokens: 1000000,
|
|
66
|
+
max_runtime_minutes: 120,
|
|
67
|
+
max_changed_files: 60,
|
|
68
|
+
max_diff_lines: 6000
|
|
69
|
+
}),
|
|
70
|
+
autopilot: Object.freeze({
|
|
71
|
+
max_steps: 50,
|
|
72
|
+
error_streak_limit: 8,
|
|
73
|
+
cost_ceiling_tokens: 3000000,
|
|
74
|
+
max_runtime_minutes: 360,
|
|
75
|
+
max_changed_files: null,
|
|
76
|
+
max_diff_lines: null
|
|
77
|
+
})
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const KNOWN_TOP_FIELDS = new Set([
|
|
81
|
+
'feature', 'contract_mode', 'governor', 'criteria',
|
|
82
|
+
'allowed_files', 'forbidden_files', 'human_gate'
|
|
83
|
+
]);
|
|
84
|
+
const KNOWN_GOVERNOR_FIELDS = new Set([
|
|
85
|
+
'max_steps', 'error_streak_limit', 'cost_ceiling_tokens',
|
|
86
|
+
'max_runtime_minutes', 'max_changed_files', 'max_diff_lines'
|
|
87
|
+
]);
|
|
88
|
+
const KNOWN_HUMAN_GATE_FIELDS = new Set(['required_for', 'themes']);
|
|
89
|
+
const KNOWN_THEME_FIELDS = new Set(['name', 'paths']);
|
|
90
|
+
const KNOWN_CRITERIA_FIELDS = new Set([
|
|
91
|
+
'id', 'description', 'assertion', 'binary', 'verification',
|
|
92
|
+
// SG-* static criteria (build-independent): proven by reading files, not running a command.
|
|
93
|
+
'must_match', 'must_not_match', 'files'
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
const VALID_MODES = new Set(['balanced', 'safe', 'builder', 'autopilot']);
|
|
97
|
+
|
|
98
|
+
function isPlainObject(v) {
|
|
99
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isPositiveInt(v) {
|
|
103
|
+
return Number.isInteger(v) && v > 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function checkOptionalLimit(errors, field, value) {
|
|
107
|
+
if (value === undefined || value === null) return;
|
|
108
|
+
if (!isPositiveInt(value)) {
|
|
109
|
+
errors.push({ field, reason: 'must be a positive integer or null' });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function checkGlobArray(errors, field, value) {
|
|
114
|
+
if (!Array.isArray(value)) {
|
|
115
|
+
errors.push({ field, reason: 'must be an array of glob strings' });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
value.forEach((pattern, i) => {
|
|
119
|
+
const result = validateGlobPattern(pattern);
|
|
120
|
+
if (!result.ok) {
|
|
121
|
+
errors.push({ field: `${field}[${i}]`, reason: result.reason });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Valida o contrato. Retorna { ok, errors: [{field, reason}], warnings: [{field, reason}] }.
|
|
128
|
+
* `ok === false` quando há ao menos um erro — o preflight deve encerrar antes
|
|
129
|
+
* de qualquer execução (REQ-1).
|
|
130
|
+
*/
|
|
131
|
+
function validateContract(contract) {
|
|
132
|
+
const errors = [];
|
|
133
|
+
const warnings = [];
|
|
134
|
+
|
|
135
|
+
if (!isPlainObject(contract)) {
|
|
136
|
+
return { ok: false, errors: [{ field: '(root)', reason: 'contract must be a JSON object' }], warnings };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (const key of Object.keys(contract)) {
|
|
140
|
+
if (!KNOWN_TOP_FIELDS.has(key)) {
|
|
141
|
+
errors.push({ field: key, reason: 'unknown field — check for typos (contract schema invalid)' });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (typeof contract.feature !== 'string' || !contract.feature.trim()) {
|
|
146
|
+
errors.push({ field: 'feature', reason: 'must be a non-empty string' });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (contract.contract_mode !== undefined) {
|
|
150
|
+
if (typeof contract.contract_mode !== 'string' || !VALID_MODES.has(contract.contract_mode.toLowerCase())) {
|
|
151
|
+
errors.push({ field: 'contract_mode', reason: 'must be one of: balanced, safe, builder, autopilot' });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!isPlainObject(contract.governor)) {
|
|
156
|
+
errors.push({ field: 'governor', reason: 'must be an object' });
|
|
157
|
+
} else {
|
|
158
|
+
for (const key of Object.keys(contract.governor)) {
|
|
159
|
+
if (!KNOWN_GOVERNOR_FIELDS.has(key)) {
|
|
160
|
+
errors.push({ field: `governor.${key}`, reason: 'unknown field — check for typos (contract schema invalid)' });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const g = contract.governor;
|
|
164
|
+
if (g.max_steps !== undefined && g.max_steps !== null && !(Number.isInteger(g.max_steps) && g.max_steps >= 0)) {
|
|
165
|
+
errors.push({ field: 'governor.max_steps', reason: 'must be a non-negative integer' });
|
|
166
|
+
}
|
|
167
|
+
if (g.error_streak_limit !== undefined && g.error_streak_limit !== null && !(Number.isInteger(g.error_streak_limit) && g.error_streak_limit >= 0)) {
|
|
168
|
+
errors.push({ field: 'governor.error_streak_limit', reason: 'must be a non-negative integer' });
|
|
169
|
+
}
|
|
170
|
+
checkOptionalLimit(errors, 'governor.cost_ceiling_tokens', g.cost_ceiling_tokens);
|
|
171
|
+
checkOptionalLimit(errors, 'governor.max_runtime_minutes', g.max_runtime_minutes);
|
|
172
|
+
checkOptionalLimit(errors, 'governor.max_changed_files', g.max_changed_files);
|
|
173
|
+
checkOptionalLimit(errors, 'governor.max_diff_lines', g.max_diff_lines);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (contract.allowed_files !== undefined) {
|
|
177
|
+
checkGlobArray(errors, 'allowed_files', contract.allowed_files);
|
|
178
|
+
// EC-5: allowlist vazia bloquearia tudo — warning + tratada como ausente
|
|
179
|
+
if (Array.isArray(contract.allowed_files) && contract.allowed_files.length === 0) {
|
|
180
|
+
warnings.push({
|
|
181
|
+
field: 'allowed_files',
|
|
182
|
+
reason: 'empty allowlist would block every write — treated as absent'
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (contract.forbidden_files !== undefined) {
|
|
188
|
+
checkGlobArray(errors, 'forbidden_files', contract.forbidden_files);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (contract.human_gate !== undefined) {
|
|
192
|
+
if (!isPlainObject(contract.human_gate)) {
|
|
193
|
+
errors.push({ field: 'human_gate', reason: 'must be an object' });
|
|
194
|
+
} else {
|
|
195
|
+
for (const key of Object.keys(contract.human_gate)) {
|
|
196
|
+
if (!KNOWN_HUMAN_GATE_FIELDS.has(key)) {
|
|
197
|
+
errors.push({ field: `human_gate.${key}`, reason: 'unknown field — check for typos (contract schema invalid)' });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const hg = contract.human_gate;
|
|
201
|
+
if (!Array.isArray(hg.required_for)) {
|
|
202
|
+
errors.push({ field: 'human_gate.required_for', reason: 'must be an array of themes (required when human_gate is present)' });
|
|
203
|
+
} else {
|
|
204
|
+
hg.required_for.forEach((theme, i) => {
|
|
205
|
+
if (!HUMAN_GATE_THEMES.includes(theme)) {
|
|
206
|
+
errors.push({ field: `human_gate.required_for[${i}]`, reason: `unknown theme "${theme}" — valid: ${HUMAN_GATE_THEMES.join(', ')}` });
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
if (hg.themes !== undefined) {
|
|
211
|
+
if (!Array.isArray(hg.themes)) {
|
|
212
|
+
errors.push({ field: 'human_gate.themes', reason: 'must be an array' });
|
|
213
|
+
} else {
|
|
214
|
+
hg.themes.forEach((theme, i) => {
|
|
215
|
+
if (!isPlainObject(theme)) {
|
|
216
|
+
errors.push({ field: `human_gate.themes[${i}]`, reason: 'must be an object { name, paths }' });
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
for (const key of Object.keys(theme)) {
|
|
220
|
+
if (!KNOWN_THEME_FIELDS.has(key)) {
|
|
221
|
+
errors.push({ field: `human_gate.themes[${i}].${key}`, reason: 'unknown field — check for typos (contract schema invalid)' });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (!HUMAN_GATE_THEMES.includes(theme.name)) {
|
|
225
|
+
errors.push({ field: `human_gate.themes[${i}].name`, reason: `unknown theme "${theme.name}" — valid: ${HUMAN_GATE_THEMES.join(', ')}` });
|
|
226
|
+
}
|
|
227
|
+
checkGlobArray(errors, `human_gate.themes[${i}].paths`, theme.paths);
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (contract.criteria !== undefined) {
|
|
235
|
+
if (!Array.isArray(contract.criteria)) {
|
|
236
|
+
errors.push({ field: 'criteria', reason: 'must be an array' });
|
|
237
|
+
} else {
|
|
238
|
+
contract.criteria.forEach((criterion, i) => {
|
|
239
|
+
if (!isPlainObject(criterion)) {
|
|
240
|
+
errors.push({ field: `criteria[${i}]`, reason: 'must be an object' });
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
for (const key of Object.keys(criterion)) {
|
|
244
|
+
if (!KNOWN_CRITERIA_FIELDS.has(key)) {
|
|
245
|
+
errors.push({ field: `criteria[${i}].${key}`, reason: 'unknown field — check for typos (contract schema invalid)' });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (typeof criterion.id !== 'string' || !criterion.id.trim()) {
|
|
249
|
+
errors.push({ field: `criteria[${i}].id`, reason: 'must be a non-empty string' });
|
|
250
|
+
}
|
|
251
|
+
if (criterion.verification !== undefined && (typeof criterion.verification !== 'string' || !criterion.verification.trim())) {
|
|
252
|
+
errors.push({ field: `criteria[${i}].verification`, reason: 'must be a non-empty shell command string when present' });
|
|
253
|
+
}
|
|
254
|
+
if (criterion.binary !== undefined && typeof criterion.binary !== 'boolean') {
|
|
255
|
+
errors.push({ field: `criteria[${i}].binary`, reason: 'must be a boolean' });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// SG-* static criteria — must_match / must_not_match are arrays of
|
|
259
|
+
// non-empty pattern strings; files[] is the (non-empty) set they read.
|
|
260
|
+
for (const patField of ['must_match', 'must_not_match']) {
|
|
261
|
+
if (criterion[patField] === undefined) continue;
|
|
262
|
+
if (!Array.isArray(criterion[patField])) {
|
|
263
|
+
errors.push({ field: `criteria[${i}].${patField}`, reason: 'must be an array of non-empty pattern strings' });
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
criterion[patField].forEach((pat, j) => {
|
|
267
|
+
if (typeof pat !== 'string' || !pat.trim()) {
|
|
268
|
+
errors.push({ field: `criteria[${i}].${patField}[${j}]`, reason: 'must be a non-empty string' });
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
if (criterion.files !== undefined) {
|
|
273
|
+
if (!Array.isArray(criterion.files)) {
|
|
274
|
+
errors.push({ field: `criteria[${i}].files`, reason: 'must be an array of file paths' });
|
|
275
|
+
} else {
|
|
276
|
+
criterion.files.forEach((fp, j) => {
|
|
277
|
+
if (typeof fp !== 'string' || !fp.trim()) {
|
|
278
|
+
errors.push({ field: `criteria[${i}].files[${j}]`, reason: 'must be a non-empty file path' });
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const isStatic =
|
|
284
|
+
(Array.isArray(criterion.must_match) && criterion.must_match.length > 0) ||
|
|
285
|
+
(Array.isArray(criterion.must_not_match) && criterion.must_not_match.length > 0);
|
|
286
|
+
if (isStatic) {
|
|
287
|
+
// A static criterion needs files[] to read.
|
|
288
|
+
if (!Array.isArray(criterion.files) || criterion.files.length === 0) {
|
|
289
|
+
errors.push({ field: `criteria[${i}].files`, reason: 'a static criterion (must_match / must_not_match) requires a non-empty files[] to read' });
|
|
290
|
+
}
|
|
291
|
+
// A criterion is either runtime (verification) or static (patterns), not both.
|
|
292
|
+
if (criterion.verification !== undefined) {
|
|
293
|
+
errors.push({ field: `criteria[${i}].verification`, reason: 'a criterion is either runtime (verification) or static (must_match / must_not_match), not both — split into two criteria' });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Cobertura executável: critério binário sem verification continua
|
|
298
|
+
// válido (julgado pelo @validator), mas é dívida de verificação. Um
|
|
299
|
+
// critério SG-* estático já é checável deterministicamente — não é dívida.
|
|
300
|
+
if (criterion.binary === true && criterion.verification === undefined && !isStatic) {
|
|
301
|
+
warnings.push({
|
|
302
|
+
field: `criteria[${i}].verification`,
|
|
303
|
+
reason: `binary criterion "${criterion.id}" has no executable verification command — @validator will LLM-judge it`
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return { ok: errors.length === 0, errors, warnings };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Resolve o contrato VALIDADO para sua forma efetiva:
|
|
315
|
+
* - preset do contract_mode preenche valores do governor não definidos (REQ-19);
|
|
316
|
+
* - `forbidden_files` mesclado com os defaults não-removíveis (REQ-4);
|
|
317
|
+
* - `allowed_files: []` tratado como ausente (EC-5);
|
|
318
|
+
* - mapa tema→paths resolvido (override substitui, não mescla).
|
|
319
|
+
*
|
|
320
|
+
* Não muta o contrato original; retorna um objeto efetivo para os guards.
|
|
321
|
+
*/
|
|
322
|
+
function resolveContract(contract) {
|
|
323
|
+
const mode = typeof contract.contract_mode === 'string'
|
|
324
|
+
? contract.contract_mode.toLowerCase()
|
|
325
|
+
: 'balanced';
|
|
326
|
+
const preset = CONTRACT_PRESETS[mode] || null;
|
|
327
|
+
|
|
328
|
+
const governor = { ...(contract.governor || {}) };
|
|
329
|
+
if (preset) {
|
|
330
|
+
for (const [key, value] of Object.entries(preset)) {
|
|
331
|
+
if (governor[key] === undefined) governor[key] = value;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const forbidden = [
|
|
336
|
+
...DEFAULT_FORBIDDEN_GLOBS,
|
|
337
|
+
...(Array.isArray(contract.forbidden_files) ? contract.forbidden_files : [])
|
|
338
|
+
];
|
|
339
|
+
|
|
340
|
+
const allowed = Array.isArray(contract.allowed_files) && contract.allowed_files.length > 0
|
|
341
|
+
? contract.allowed_files.slice()
|
|
342
|
+
: null;
|
|
343
|
+
|
|
344
|
+
const themePaths = { ...DEFAULT_THEME_PATHS };
|
|
345
|
+
const requiredFor = contract.human_gate && Array.isArray(contract.human_gate.required_for)
|
|
346
|
+
? contract.human_gate.required_for.slice()
|
|
347
|
+
: [];
|
|
348
|
+
if (contract.human_gate && Array.isArray(contract.human_gate.themes)) {
|
|
349
|
+
for (const theme of contract.human_gate.themes) {
|
|
350
|
+
if (theme && HUMAN_GATE_THEMES.includes(theme.name) && Array.isArray(theme.paths)) {
|
|
351
|
+
themePaths[theme.name] = theme.paths.slice();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
feature: contract.feature,
|
|
358
|
+
contract_mode: mode,
|
|
359
|
+
governor,
|
|
360
|
+
criteria: Array.isArray(contract.criteria) ? contract.criteria : [],
|
|
361
|
+
allowed_files: allowed,
|
|
362
|
+
forbidden_files: forbidden,
|
|
363
|
+
human_gate: {
|
|
364
|
+
required_for: requiredFor,
|
|
365
|
+
theme_paths: themePaths
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
module.exports = {
|
|
371
|
+
DEFAULT_FORBIDDEN_GLOBS,
|
|
372
|
+
HUMAN_GATE_THEMES,
|
|
373
|
+
DEFAULT_THEME_PATHS,
|
|
374
|
+
CONTRACT_PRESETS,
|
|
375
|
+
validateContract,
|
|
376
|
+
resolveContract
|
|
377
|
+
};
|