devlyn-cli 4.0.1 → 4.2.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/AGENTS.md +11 -95
- package/CLAUDE.md +7 -160
- package/LICENSE +21 -0
- package/README.md +83 -114
- package/bin/devlyn.js +330 -472
- package/bin/instruction-templates.json +120 -2
- package/bin/instructions.js +35 -8
- package/config/skills/_shared/adapters/claude.md +8 -85
- package/config/skills/_shared/adapters/codex.md +14 -34
- package/config/skills/_shared/adapters/omp.md +1 -11
- package/config/skills/_shared/codex-monitored.sh +4 -54
- package/config/skills/_shared/engine-doctor.sh +18 -58
- package/config/skills/_shared/engine-preflight.md +15 -47
- package/config/skills/_shared/expected-contract.py +242 -0
- package/config/skills/_shared/expected.schema.json +10 -95
- package/config/skills/_shared/invocation-receipt.py +26 -757
- package/config/skills/_shared/platform-support.py +37 -28
- package/config/skills/_shared/role-config.py +100 -421
- package/config/skills/_shared/runtime-principles.md +17 -93
- package/config/skills/_shared/task-complete.py +490 -285
- package/config/skills/_shared/task-completion.md +235 -0
- package/config/skills/devlyn-engines/SKILL.md +9 -11
- package/config/skills/devlyn-ideate/SKILL.md +57 -107
- package/config/skills/devlyn-ideate/references/elicitation.md +18 -99
- package/config/skills/devlyn-ideate/references/loop.md +110 -0
- package/config/skills/devlyn-ideate/references/package-format.md +128 -0
- package/config/skills/devlyn-ideate/scripts/acceptance.py +795 -0
- package/config/skills/devlyn-ideate/scripts/queue.py +1780 -0
- package/package.json +2 -3
- package/config/commit-conventions.md +0 -28
- package/config/skills/_shared/adapters/README.md +0 -74
- package/config/skills/_shared/adapters/grok.md +0 -100
- package/config/skills/_shared/archive_run.py +0 -969
- package/config/skills/_shared/codex-config.md +0 -81
- package/config/skills/_shared/collect-codex-findings.py +0 -505
- package/config/skills/_shared/finish-gate.py +0 -521
- package/config/skills/_shared/grok-anchor-guard.py +0 -78
- package/config/skills/_shared/judge-output-parser.py +0 -200
- package/config/skills/_shared/judge-role-evidence.py +0 -320
- package/config/skills/_shared/phase-prompt-render.py +0 -183
- package/config/skills/_shared/process-evidence.py +0 -926
- package/config/skills/_shared/resolve-bootstrap.py +0 -1410
- package/config/skills/_shared/resolve-stop-hook.py +0 -359
- package/config/skills/_shared/run-bounded.py +0 -58
- package/config/skills/_shared/spec-verify-check.py +0 -5083
- package/config/skills/_shared/state-phase-write.py +0 -5989
- package/config/skills/_shared/terminal-claim-check.py +0 -777
- package/config/skills/_shared/verify-merge-findings.py +0 -3528
- package/config/skills/devlyn-design-ui/SKILL.md +0 -650
- package/config/skills/devlyn-ideate/references/from-spec-mode.md +0 -61
- package/config/skills/devlyn-ideate/references/project-mode.md +0 -80
- package/config/skills/devlyn-ideate/references/spec-template.md +0 -121
- package/config/skills/devlyn-ideate/references/templates/decision.md +0 -48
- package/config/skills/devlyn-ideate/references/templates/roadmap.md +0 -50
- package/config/skills/devlyn-ideate/references/templates/vision.md +0 -44
- package/config/skills/devlyn-queue/SKILL.md +0 -66
- package/config/skills/devlyn-queue/scripts/append.py +0 -94
- package/config/skills/devlyn-resolve/SKILL.md +0 -379
- package/config/skills/devlyn-resolve/references/free-form-mode.md +0 -71
- package/config/skills/devlyn-resolve/references/outer-loop.md +0 -23
- package/config/skills/devlyn-resolve/references/phases/build-gate.md +0 -75
- package/config/skills/devlyn-resolve/references/phases/cleanup.md +0 -33
- package/config/skills/devlyn-resolve/references/phases/implement.md +0 -44
- package/config/skills/devlyn-resolve/references/phases/plan.md +0 -39
- package/config/skills/devlyn-resolve/references/phases/probe-derive.md +0 -261
- package/config/skills/devlyn-resolve/references/phases/surface-close.md +0 -34
- package/config/skills/devlyn-resolve/references/phases/verify.md +0 -310
- package/config/skills/devlyn-resolve/references/state-schema.md +0 -152
- package/config/skills/devlyn-resolve/references/task-completion.md +0 -160
- package/config/templates/prompt-templates.md +0 -71
- package/config/templates/template-feature.spec.md +0 -255
- package/config/templates/template-product-spec.md +0 -680
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Strict JSON and `spec.expected.json` contract validation for the ideate loop's package check and acceptance runner."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
import unittest
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def reject_json_constant(token: str) -> None:
|
|
11
|
+
raise ValueError(f"invalid JSON numeric constant: {token}")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def reject_duplicate_keys(pairs: list[tuple[str, object]]) -> dict:
|
|
15
|
+
result = {}
|
|
16
|
+
for key, value in pairs:
|
|
17
|
+
if key in result:
|
|
18
|
+
raise ValueError(f"duplicate JSON key: {key}")
|
|
19
|
+
result[key] = value
|
|
20
|
+
return result
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def loads_strict_json(text: str):
|
|
24
|
+
return json.loads(
|
|
25
|
+
text,
|
|
26
|
+
parse_constant=reject_json_constant,
|
|
27
|
+
object_pairs_hook=reject_duplicate_keys,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
EXPECTED_TOP_LEVEL_KEYS = {
|
|
32
|
+
"verification_commands",
|
|
33
|
+
"forbidden_patterns",
|
|
34
|
+
"required_files",
|
|
35
|
+
"forbidden_files",
|
|
36
|
+
"max_deps_added",
|
|
37
|
+
"pure_design",
|
|
38
|
+
}
|
|
39
|
+
EXPECTED_VERIFICATION_COMMAND_KEYS = {
|
|
40
|
+
"cmd",
|
|
41
|
+
"argv",
|
|
42
|
+
"exit_code",
|
|
43
|
+
"timeout_sec",
|
|
44
|
+
"stdout_contains",
|
|
45
|
+
"stdout_not_contains",
|
|
46
|
+
"contract_refs",
|
|
47
|
+
}
|
|
48
|
+
DEFAULT_TIMEOUT_SEC = 60
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def verification_timeout_sec(command: dict) -> int:
|
|
52
|
+
return command.get("timeout_sec", DEFAULT_TIMEOUT_SEC)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def validate_command(command: object, label: str) -> str | None:
|
|
56
|
+
"""Validate one command; `argv` and `cmd` are mutually exclusive."""
|
|
57
|
+
if not isinstance(command, dict):
|
|
58
|
+
return f"{label} must be an object"
|
|
59
|
+
if "argv" in command:
|
|
60
|
+
if "cmd" in command:
|
|
61
|
+
return f"{label} requires exactly one of cmd or argv"
|
|
62
|
+
value = command["argv"]
|
|
63
|
+
if not isinstance(value, list) or not value or not all(isinstance(item, str) for item in value) or not value[0]:
|
|
64
|
+
return f"{label}.argv must be a string array with non-empty argv[0]"
|
|
65
|
+
else:
|
|
66
|
+
cmd = command.get("cmd")
|
|
67
|
+
if not isinstance(cmd, str) or not cmd.strip():
|
|
68
|
+
return f"{label}.cmd must be a non-empty string"
|
|
69
|
+
ec = command.get("exit_code", 0)
|
|
70
|
+
if isinstance(ec, bool) or not isinstance(ec, int):
|
|
71
|
+
return f"{label}.exit_code must be int (not bool)"
|
|
72
|
+
timeout_sec = command.get("timeout_sec", DEFAULT_TIMEOUT_SEC)
|
|
73
|
+
if isinstance(timeout_sec, bool) or not isinstance(timeout_sec, int) or not 1 <= timeout_sec <= 600:
|
|
74
|
+
return f"{label}.timeout_sec must be int from 1 to 600 (not bool)"
|
|
75
|
+
for k in ("stdout_contains", "stdout_not_contains"):
|
|
76
|
+
v = command.get(k, [])
|
|
77
|
+
if not isinstance(v, list) or not all(isinstance(s, str) and s for s in v):
|
|
78
|
+
return f"{label}.{k} must be a list of non-empty strings"
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def validate_shape(data) -> str | None:
|
|
83
|
+
"""Return None if `data` has a non-empty, well-formed `verification_commands`
|
|
84
|
+
list; else a human-readable error string.
|
|
85
|
+
|
|
86
|
+
Each object requires exactly one of a non-empty string `cmd` or a
|
|
87
|
+
string-array `argv`; `exit_code` defaults to 0 and must be a
|
|
88
|
+
non-bool int; `timeout_sec` defaults to DEFAULT_TIMEOUT_SEC and must be a
|
|
89
|
+
non-bool int from 1 through 600; `stdout_contains` and `stdout_not_contains`
|
|
90
|
+
default to empty lists of non-empty strings. Bool is rejected explicitly
|
|
91
|
+
because Python's `bool` subclasses `int`.
|
|
92
|
+
"""
|
|
93
|
+
if not isinstance(data, dict):
|
|
94
|
+
return "top-level must be a JSON object"
|
|
95
|
+
cmds = data.get("verification_commands")
|
|
96
|
+
if not isinstance(cmds, list):
|
|
97
|
+
return "verification_commands must be a list"
|
|
98
|
+
if not cmds:
|
|
99
|
+
return "verification_commands must contain at least one entry"
|
|
100
|
+
for i, c in enumerate(cmds):
|
|
101
|
+
err = validate_command(c, f"verification_commands[{i}]")
|
|
102
|
+
if err:
|
|
103
|
+
return err
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def validate_string_list(data: object, key: str) -> str | None:
|
|
108
|
+
value = data.get(key, []) if isinstance(data, dict) else None
|
|
109
|
+
if not isinstance(value, list) or not all(isinstance(item, str) and item for item in value):
|
|
110
|
+
return f"{key} must be a list of non-empty strings"
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def validate_expected_shape(data) -> str | None:
|
|
115
|
+
"""Return None if shape matches the sibling spec.expected.json schema.
|
|
116
|
+
|
|
117
|
+
Keep this dependency-free: it mirrors `_shared/expected.schema.json` enough
|
|
118
|
+
to catch malformed contracts before a consumer executes them.
|
|
119
|
+
"""
|
|
120
|
+
if not isinstance(data, dict):
|
|
121
|
+
return "top-level must be a JSON object"
|
|
122
|
+
unknown = sorted(set(data) - EXPECTED_TOP_LEVEL_KEYS)
|
|
123
|
+
if unknown:
|
|
124
|
+
return f"unknown top-level key(s): {', '.join(unknown)}"
|
|
125
|
+
if "verification_commands" in data:
|
|
126
|
+
commands = data["verification_commands"]
|
|
127
|
+
if not isinstance(commands, list):
|
|
128
|
+
return "verification_commands must be a list"
|
|
129
|
+
if commands:
|
|
130
|
+
err = validate_shape({"verification_commands": commands})
|
|
131
|
+
if err:
|
|
132
|
+
return err
|
|
133
|
+
for i, command in enumerate(commands):
|
|
134
|
+
unknown_command_keys = sorted(set(command) - EXPECTED_VERIFICATION_COMMAND_KEYS)
|
|
135
|
+
if unknown_command_keys:
|
|
136
|
+
return (
|
|
137
|
+
f"verification_commands[{i}] unknown key(s): "
|
|
138
|
+
f"{', '.join(unknown_command_keys)}"
|
|
139
|
+
)
|
|
140
|
+
contract_refs = command.get("contract_refs", [])
|
|
141
|
+
if not isinstance(contract_refs, list) or not all(
|
|
142
|
+
isinstance(item, str) and item for item in contract_refs
|
|
143
|
+
):
|
|
144
|
+
return f"verification_commands[{i}].contract_refs must be a list of non-empty strings"
|
|
145
|
+
for key in ("required_files", "forbidden_files"):
|
|
146
|
+
err = validate_string_list(data, key)
|
|
147
|
+
if err:
|
|
148
|
+
return err
|
|
149
|
+
max_deps = data.get("max_deps_added", 0)
|
|
150
|
+
if isinstance(max_deps, bool) or not isinstance(max_deps, int) or max_deps < 0:
|
|
151
|
+
return "max_deps_added must be a non-negative integer"
|
|
152
|
+
if "pure_design" in data and not isinstance(data["pure_design"], bool):
|
|
153
|
+
return "pure_design must be a boolean"
|
|
154
|
+
patterns = data.get("forbidden_patterns", [])
|
|
155
|
+
if not isinstance(patterns, list):
|
|
156
|
+
return "forbidden_patterns must be a list"
|
|
157
|
+
for i, pattern in enumerate(patterns):
|
|
158
|
+
if not isinstance(pattern, dict):
|
|
159
|
+
return f"forbidden_patterns[{i}] must be an object"
|
|
160
|
+
unknown_pattern_keys = sorted(set(pattern) - {"pattern", "description", "files", "severity"})
|
|
161
|
+
if unknown_pattern_keys:
|
|
162
|
+
return (
|
|
163
|
+
f"forbidden_patterns[{i}] unknown key(s): "
|
|
164
|
+
f"{', '.join(unknown_pattern_keys)}"
|
|
165
|
+
)
|
|
166
|
+
for key in ("pattern", "description", "severity"):
|
|
167
|
+
value = pattern.get(key)
|
|
168
|
+
if not isinstance(value, str) or not value:
|
|
169
|
+
return f"forbidden_patterns[{i}].{key} must be a non-empty string"
|
|
170
|
+
if pattern["severity"] not in {"disqualifier", "warning"}:
|
|
171
|
+
return f"forbidden_patterns[{i}].severity must be disqualifier or warning"
|
|
172
|
+
files = pattern.get("files", [])
|
|
173
|
+
if not isinstance(files, list) or not all(isinstance(item, str) and item for item in files):
|
|
174
|
+
return f"forbidden_patterns[{i}].files must be a list of non-empty strings"
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def slice_diff_to_files(diff_text: str, files: list[str]) -> str:
|
|
179
|
+
if not files:
|
|
180
|
+
return diff_text
|
|
181
|
+
out: list[str] = []
|
|
182
|
+
keep = False
|
|
183
|
+
for line in diff_text.splitlines(keepends=True):
|
|
184
|
+
if line.startswith("diff --git "):
|
|
185
|
+
keep = any(path in line for path in files)
|
|
186
|
+
if keep:
|
|
187
|
+
out.append(line)
|
|
188
|
+
return "".join(out)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class ContractTests(unittest.TestCase):
|
|
192
|
+
def test_strict_json(self):
|
|
193
|
+
for text, message in (('{"a":1,"a":2}', "duplicate JSON key"), ('{"a":NaN}', "invalid JSON numeric constant")):
|
|
194
|
+
with self.assertRaisesRegex(ValueError, message):
|
|
195
|
+
loads_strict_json(text)
|
|
196
|
+
|
|
197
|
+
def test_cmd_and_argv_are_mutually_exclusive(self):
|
|
198
|
+
ok = {"verification_commands": [{"argv": ["python3", "-c", "pass"], "contract_refs": ["R1"]}, {"cmd": "true"}]}
|
|
199
|
+
self.assertIsNone(validate_expected_shape(ok))
|
|
200
|
+
for command, message in (
|
|
201
|
+
({"cmd": "true", "argv": ["true"]}, "exactly one of cmd or argv"),
|
|
202
|
+
({"argv": []}, "argv must be a string array"),
|
|
203
|
+
({"argv": ["", "x"]}, "argv must be a string array"),
|
|
204
|
+
({"argv": ["x", 1]}, "argv must be a string array"),
|
|
205
|
+
({}, "cmd must be a non-empty string"),
|
|
206
|
+
):
|
|
207
|
+
self.assertIn(message, validate_expected_shape({"verification_commands": [command]}))
|
|
208
|
+
|
|
209
|
+
def test_command_fields(self):
|
|
210
|
+
for command, message in (
|
|
211
|
+
({"cmd": "x", "exit_code": True}, "exit_code must be int"),
|
|
212
|
+
({"cmd": "x", "timeout_sec": 601}, "timeout_sec must be int from 1 to 600"),
|
|
213
|
+
({"cmd": "x", "stdout_contains": "x"}, "stdout_contains must be a list of non-empty strings"),
|
|
214
|
+
({"cmd": "x", "stdout_contains": [""]}, "stdout_contains must be a list of non-empty strings"),
|
|
215
|
+
({"cmd": "x", "stdout_not_contains": [""]}, "stdout_not_contains must be a list of non-empty strings"),
|
|
216
|
+
({"cmd": "x", "contract_refs": [""]}, "contract_refs must be a list of non-empty strings"),
|
|
217
|
+
({"cmd": "x", "extra": 1}, "unknown key(s): extra"),
|
|
218
|
+
):
|
|
219
|
+
self.assertIn(message, validate_expected_shape({"verification_commands": [command]}))
|
|
220
|
+
self.assertEqual(validate_expected_shape([]), "top-level must be a JSON object")
|
|
221
|
+
self.assertIn("unknown top-level key(s): process_evidence", validate_expected_shape({"process_evidence": []}))
|
|
222
|
+
|
|
223
|
+
def test_diff_helpers(self):
|
|
224
|
+
diff = ('diff --git a/package.json b/package.json\n--- a/package.json\n+++ b/package.json\n@@ -1,3 +1,5 @@\n'
|
|
225
|
+
' {\n "dependencies": {\n+ "left-pad": "1.0.0",\n+ "is-odd": "3.0.0"\n }\n }\n'
|
|
226
|
+
'diff --git a/other b/other\n+x\n')
|
|
227
|
+
self.assertNotIn("other", slice_diff_to_files(diff, ["package.json"]))
|
|
228
|
+
self.assertEqual(slice_diff_to_files(diff, []), diff)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def main() -> int:
|
|
232
|
+
if sys.argv[1:] != ["--self-test"]:
|
|
233
|
+
print("usage: expected-contract.py --self-test", file=sys.stderr)
|
|
234
|
+
return 2
|
|
235
|
+
result = unittest.TextTestRunner(verbosity=1).run(unittest.defaultTestLoader.loadTestsFromTestCase(ContractTests))
|
|
236
|
+
if result.wasSuccessful():
|
|
237
|
+
print(f"expected-contract self-test: PASS ({result.testsRun} tests)")
|
|
238
|
+
return 0 if result.wasSuccessful() else 1
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
if __name__ == "__main__":
|
|
242
|
+
sys.exit(main())
|
|
@@ -6,79 +6,27 @@
|
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
8
|
"properties": {
|
|
9
|
-
"
|
|
9
|
+
"verification_commands": {
|
|
10
10
|
"type": "array",
|
|
11
|
-
"description": "
|
|
11
|
+
"description": "Each command must pass on the task's committed source. At least one entry is required when the spec has any observable runtime check (CLI, test command, HTTP request).",
|
|
12
12
|
"items": {
|
|
13
13
|
"type": "object",
|
|
14
14
|
"additionalProperties": false,
|
|
15
|
-
"required": ["id", "phase"],
|
|
16
15
|
"oneOf": [
|
|
17
16
|
{ "required": ["cmd"], "not": { "required": ["argv"] } },
|
|
18
17
|
{ "required": ["argv"], "not": { "required": ["cmd"] } }
|
|
19
18
|
],
|
|
20
19
|
"properties": {
|
|
21
|
-
"id": {
|
|
22
|
-
"type": "string",
|
|
23
|
-
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$",
|
|
24
|
-
"description": "Stable evidence id, unique within its phase and safe as one path component."
|
|
25
|
-
},
|
|
26
|
-
"phase": {
|
|
27
|
-
"const": "implement",
|
|
28
|
-
"description": "The phase that must produce this expected-contract obligation."
|
|
29
|
-
},
|
|
30
20
|
"cmd": {
|
|
31
21
|
"type": "string",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
22
|
+
"description": "Shell command, executed via `subprocess.run(..., shell=True)` from the build's working directory.",
|
|
23
|
+
"minLength": 1
|
|
34
24
|
},
|
|
35
25
|
"argv": {
|
|
36
26
|
"type": "array",
|
|
37
27
|
"minItems": 1,
|
|
38
28
|
"items": { "type": "string" },
|
|
39
|
-
"description": "Exact argv executed without a shell; argv[0] must be non-empty."
|
|
40
|
-
},
|
|
41
|
-
"exit_code": {
|
|
42
|
-
"type": "integer",
|
|
43
|
-
"minimum": 0,
|
|
44
|
-
"description": "Required exit code. Default 0 if omitted.",
|
|
45
|
-
"default": 0
|
|
46
|
-
},
|
|
47
|
-
"timeout_sec": {
|
|
48
|
-
"type": "integer",
|
|
49
|
-
"minimum": 1,
|
|
50
|
-
"maximum": 600,
|
|
51
|
-
"description": "Per-command timeout in seconds. Default 60 if omitted.",
|
|
52
|
-
"default": 60
|
|
53
|
-
},
|
|
54
|
-
"stdout_contains": {
|
|
55
|
-
"type": "array",
|
|
56
|
-
"items": { "type": "string", "minLength": 1 },
|
|
57
|
-
"description": "Each UTF-8 substring must occur in the concatenated raw stdout and stderr bytes.",
|
|
58
|
-
"default": []
|
|
59
|
-
},
|
|
60
|
-
"stdout_not_contains": {
|
|
61
|
-
"type": "array",
|
|
62
|
-
"items": { "type": "string", "minLength": 1 },
|
|
63
|
-
"description": "No UTF-8 substring may occur in the concatenated raw stdout and stderr bytes.",
|
|
64
|
-
"default": []
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
"default": []
|
|
69
|
-
},
|
|
70
|
-
"verification_commands": {
|
|
71
|
-
"type": "array",
|
|
72
|
-
"description": "Each command is executed against the post-BUILD code. Each pass/fail contributes to verify_score. At least one entry is required when the spec has any observable runtime check (CLI, test command, HTTP request).",
|
|
73
|
-
"items": {
|
|
74
|
-
"type": "object",
|
|
75
|
-
"additionalProperties": false,
|
|
76
|
-
"required": ["cmd"],
|
|
77
|
-
"properties": {
|
|
78
|
-
"cmd": {
|
|
79
|
-
"type": "string",
|
|
80
|
-
"description": "Shell command, executed via `subprocess.run(..., shell=True)` from the build's working directory.",
|
|
81
|
-
"minLength": 1
|
|
29
|
+
"description": "Exact argv executed without a shell; argv[0] must be non-empty. Preferred for generated and Windows contracts."
|
|
82
30
|
},
|
|
83
31
|
"exit_code": {
|
|
84
32
|
"type": "integer",
|
|
@@ -106,7 +54,7 @@
|
|
|
106
54
|
},
|
|
107
55
|
"contract_refs": {
|
|
108
56
|
"type": "array",
|
|
109
|
-
"description": "
|
|
57
|
+
"description": "Requirements this command verifies. Ideate loop tasks name requirement IDs (R1, R2, ...) from the task Requirements and require at least one.",
|
|
110
58
|
"items": { "type": "string", "minLength": 1 },
|
|
111
59
|
"default": []
|
|
112
60
|
}
|
|
@@ -115,7 +63,7 @@
|
|
|
115
63
|
},
|
|
116
64
|
"forbidden_patterns": {
|
|
117
65
|
"type": "array",
|
|
118
|
-
"description": "Regex patterns scanned across
|
|
66
|
+
"description": "Regex patterns scanned across the task's diff.",
|
|
119
67
|
"items": {
|
|
120
68
|
"type": "object",
|
|
121
69
|
"additionalProperties": false,
|
|
@@ -140,14 +88,14 @@
|
|
|
140
88
|
"severity": {
|
|
141
89
|
"type": "string",
|
|
142
90
|
"enum": ["disqualifier", "warning"],
|
|
143
|
-
"description": "disqualifier
|
|
91
|
+
"description": "disqualifier blocks acceptance; warning is recorded."
|
|
144
92
|
}
|
|
145
93
|
}
|
|
146
94
|
}
|
|
147
95
|
},
|
|
148
96
|
"required_files": {
|
|
149
97
|
"type": "array",
|
|
150
|
-
"description": "Files specified by literal paths that must exist
|
|
98
|
+
"description": "Files specified by literal paths that must exist in the committed source; globs are not expanded.",
|
|
151
99
|
"items": { "type": "string", "minLength": 1 },
|
|
152
100
|
"default": []
|
|
153
101
|
},
|
|
@@ -157,21 +105,9 @@
|
|
|
157
105
|
"items": { "type": "string", "minLength": 1 },
|
|
158
106
|
"default": []
|
|
159
107
|
},
|
|
160
|
-
"tier_a_waivers": {
|
|
161
|
-
"type": "array",
|
|
162
|
-
"description": "Optional fnmatch globs exempted from Tier A scope-oracle path checks when the spec explicitly authorizes those files.",
|
|
163
|
-
"items": { "type": "string", "minLength": 1 },
|
|
164
|
-
"default": []
|
|
165
|
-
},
|
|
166
|
-
"spec_output_files": {
|
|
167
|
-
"type": "array",
|
|
168
|
-
"description": "Files or globs that define the spec-authorized output surface for scope oracles. Touched files outside this set must be reachable from it via static imports or separately waived.",
|
|
169
|
-
"items": { "type": "string", "minLength": 1 },
|
|
170
|
-
"default": []
|
|
171
|
-
},
|
|
172
108
|
"max_deps_added": {
|
|
173
109
|
"type": "integer",
|
|
174
|
-
"description": "Hard cap on
|
|
110
|
+
"description": "Hard cap on package names the root package.json's dependencies and devDependencies gain over the task inputs; a move between them adds none.",
|
|
175
111
|
"minimum": 0,
|
|
176
112
|
"default": 0
|
|
177
113
|
},
|
|
@@ -179,27 +115,6 @@
|
|
|
179
115
|
"type": "boolean",
|
|
180
116
|
"description": "Declares that every Requirement is pure-design (no observable runtime check). Lets verification_commands stay empty without failing shape validation. Contradictory (and rejected) when true alongside a non-empty verification_commands.",
|
|
181
117
|
"default": false
|
|
182
|
-
},
|
|
183
|
-
"required_risk_probe_requirements": {
|
|
184
|
-
"type": "array",
|
|
185
|
-
"description": "Language-neutral replacement for prose-keyword risk-probe classification: explicit {tag, derived_from} obligations the spec author declares directly. When `--risk-probes` is enabled, risk-probes.jsonl must include a probe whose tags include `tag` and whose derived_from exactly equals `derived_from` for each entry.",
|
|
186
|
-
"items": {
|
|
187
|
-
"type": "object",
|
|
188
|
-
"additionalProperties": false,
|
|
189
|
-
"required": ["tag", "derived_from"],
|
|
190
|
-
"properties": {
|
|
191
|
-
"tag": {
|
|
192
|
-
"type": "string",
|
|
193
|
-
"description": "One of the risk-probe tags defined in _shared/spec-verify-check.py:RISK_PROBE_TAGS (e.g. ordering_inversion, rollback_state, shape_contract)."
|
|
194
|
-
},
|
|
195
|
-
"derived_from": {
|
|
196
|
-
"type": "string",
|
|
197
|
-
"description": "Exact substring of the sibling spec.md's visible verification section that demands this tag.",
|
|
198
|
-
"minLength": 1
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
},
|
|
202
|
-
"default": []
|
|
203
118
|
}
|
|
204
119
|
}
|
|
205
120
|
}
|