its-magic 0.1.2-33 → 0.1.2-35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/installer.py +44 -9
- package/package.json +2 -1
- package/scripts/doc_profile_lib.py +415 -0
- package/template/.cursor/agents/po.mdc +12 -0
- package/template/.cursor/commands/ask.md +3 -3
- package/template/.cursor/commands/execute.md +12 -1
- package/template/.cursor/commands/intake.md +49 -6
- package/template/.cursor/commands/status-reconcile.md +2 -1
- package/template/.cursor/rules/core.mdc +6 -0
- package/template/.cursor/scratchpad.local.example.md +2 -0
- package/template/.cursor/scratchpad.md +2 -0
- package/template/README.md +17 -0
- package/template/docs/engineering/runbook.md +29 -0
- package/template/docs/product/backlog.md +4 -0
package/README.md
CHANGED
|
@@ -360,6 +360,23 @@ Intake artifacts must persist coverage evidence fields:
|
|
|
360
360
|
- `missing_topics`
|
|
361
361
|
- `assumptions_confirmed`
|
|
362
362
|
|
|
363
|
+
### Interactive intake evidence + validator (US-0078 / DEC-0060)
|
|
364
|
+
|
|
365
|
+
**US-0078** closes silent persistence: every intake that mutates backlog/acceptance must pass the
|
|
366
|
+
deterministic **`intake_evidence`** gate — **`topic_coverage`** with valid **`ie:`** refs,
|
|
367
|
+
asked-vs-covered alignment, and **`assumption_confirmation_ref`** when assumptions are affirmative.
|
|
368
|
+
|
|
369
|
+
- Run `python scripts/intake_evidence_validate.py --self-test` (also exercised via `tests/run-tests.*` §26k).
|
|
370
|
+
- Operator docs: **`decisions/DEC-0060.md`**, **`docs/engineering/architecture.md`** **`# US-0078`**, runbook section **Interactive intake evidence validation (US-0078 / DEC-0060)**.
|
|
371
|
+
- **Guided** and **low-touch** share the **same pre-persistence validation pipeline**; low-touch does not bypass mandatory pack coverage.
|
|
372
|
+
|
|
373
|
+
### Bug issues + intake routing (US-0079 / DEC-0061)
|
|
374
|
+
|
|
375
|
+
Defects use **`BUG-####`** under **`docs/product/backlog.md`** **`## Bug issues (canonical)`** with **`OPEN`/`DONE`** only and minimum reproducibility fields. Intake must not silently file defect prose as **`US-xxxx`**: set merged scratchpad **`INTAKE_WORK_ITEM_KIND=bug`** and/or use **`/intake bug`**, then run **`python scripts/intake_bug_routing_guard.py --kind story --file <prose.txt>`** before story allocation when in doubt.
|
|
376
|
+
|
|
377
|
+
- Validators: `python scripts/bug_issue_validate.py --self-test`; `python scripts/bug_issue_validate.py --backlog docs/product/backlog.md --check-acceptance`.
|
|
378
|
+
- Operator docs: **`decisions/DEC-0061.md`**, **`docs/engineering/architecture.md`** **`# US-0079`**, runbook **Bug issues (US-0079 / DEC-0061)**.
|
|
379
|
+
|
|
363
380
|
### Optional ID namespace bootstrap (US-0052)
|
|
364
381
|
|
|
365
382
|
Fresh-project ID bootstrap behavior is explicit and default-off:
|
package/installer.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import filecmp
|
|
3
|
+
import importlib.util
|
|
3
4
|
import json
|
|
4
5
|
import os
|
|
5
6
|
import re
|
|
@@ -280,13 +281,42 @@ def materialize_scratchpad_baseline(target_root, source_root, mode, print_ok=Tru
|
|
|
280
281
|
return True
|
|
281
282
|
|
|
282
283
|
|
|
284
|
+
def _load_doc_profile_lib():
|
|
285
|
+
"""
|
|
286
|
+
Load doc_profile_lib from scripts/ adjacent to this installer (repo checkout or npm package root).
|
|
287
|
+
Path is derived from __file__ only (no cwd / PYTHONPATH dependency).
|
|
288
|
+
"""
|
|
289
|
+
here = os.path.dirname(os.path.abspath(__file__))
|
|
290
|
+
path = os.path.join(here, "scripts", "doc_profile_lib.py")
|
|
291
|
+
if not os.path.isfile(path):
|
|
292
|
+
raise RuntimeError(
|
|
293
|
+
"[DOC_PROFILE_LIB_MISSING] Expected documentation profile library at "
|
|
294
|
+
f"{path} (same directory as installer.py). "
|
|
295
|
+
"Global installs require this file in the published its-magic package; "
|
|
296
|
+
f"reinstall or upgrade its-magic ({REPO_URL})."
|
|
297
|
+
)
|
|
298
|
+
spec = importlib.util.spec_from_file_location("doc_profile_lib", path)
|
|
299
|
+
if spec is None or spec.loader is None:
|
|
300
|
+
raise RuntimeError(
|
|
301
|
+
"[DOC_PROFILE_LIB_LOAD_ERROR] Could not create import spec for "
|
|
302
|
+
f"{path}. Reinstall its-magic ({REPO_URL})."
|
|
303
|
+
)
|
|
304
|
+
mod = importlib.util.module_from_spec(spec)
|
|
305
|
+
sys.modules["doc_profile_lib"] = mod
|
|
306
|
+
try:
|
|
307
|
+
spec.loader.exec_module(mod)
|
|
308
|
+
except Exception as e:
|
|
309
|
+
sys.modules.pop("doc_profile_lib", None)
|
|
310
|
+
raise RuntimeError(
|
|
311
|
+
"[DOC_PROFILE_LIB_LOAD_ERROR] doc_profile_lib failed to load "
|
|
312
|
+
f"({e!r}). Reinstall its-magic ({REPO_URL})."
|
|
313
|
+
) from e
|
|
314
|
+
return mod
|
|
315
|
+
|
|
316
|
+
|
|
283
317
|
def _doc_profile_sync(target_root, merged, print_ok=True):
|
|
284
318
|
"""Append missing normative README/developer doc sections from merged profile (non-destructive)."""
|
|
285
|
-
|
|
286
|
-
if scripts_dir not in sys.path:
|
|
287
|
-
sys.path.insert(0, scripts_dir)
|
|
288
|
-
import doc_profile_lib
|
|
289
|
-
|
|
319
|
+
doc_profile_lib = _load_doc_profile_lib()
|
|
290
320
|
notes = doc_profile_lib.ensure_doc_surfaces_merged(merged, target_root, print_ok=print_ok)
|
|
291
321
|
bad = [ln for ln in notes if ln.startswith("[DOC_PROFILE_INVALID]")]
|
|
292
322
|
return (not bad), notes
|
|
@@ -302,11 +332,16 @@ def run_scratchpad_postinstall(target_root, source_root, mode, print_ok=True):
|
|
|
302
332
|
print(line)
|
|
303
333
|
if ok:
|
|
304
334
|
merged, _paths = merge_scratchpad_layers(target_root)
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
335
|
+
try:
|
|
336
|
+
dp_ok, dp_notes = _doc_profile_sync(target_root, merged, print_ok=print_ok)
|
|
337
|
+
except RuntimeError as e:
|
|
338
|
+
print(str(e))
|
|
309
339
|
ok = False
|
|
340
|
+
else:
|
|
341
|
+
for line in dp_notes:
|
|
342
|
+
print(line)
|
|
343
|
+
if not dp_ok:
|
|
344
|
+
ok = False
|
|
310
345
|
if ok and print_ok:
|
|
311
346
|
loc = os.path.join(target_root, SCRATCHPAD_LOCAL_REL)
|
|
312
347
|
if os.path.isfile(loc):
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "its-magic",
|
|
3
|
-
"version": "0.1.2-
|
|
3
|
+
"version": "0.1.2-35",
|
|
4
4
|
"description": "its-magic - AI dev team workflow for Cursor.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"installer.ps1",
|
|
12
12
|
"installer.sh",
|
|
13
13
|
"installer.py",
|
|
14
|
+
"scripts/doc_profile_lib.py",
|
|
14
15
|
"bin/its-magic.js",
|
|
15
16
|
"bin/postinstall.js"
|
|
16
17
|
],
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Documentation profile resolution and surface helpers (DEC-0059).
|
|
3
|
+
|
|
4
|
+
Shared by installer (optional surface sync) and scripts/validate_doc_profile.py.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from typing import Dict, List, Optional, Set, Tuple
|
|
11
|
+
|
|
12
|
+
DOC_AUDIENCE_ALLOWED = frozenset({"user", "developer", "both"})
|
|
13
|
+
DOC_DETAIL_ALLOWED = frozenset({"concise", "balanced", "technical-deep"})
|
|
14
|
+
|
|
15
|
+
USER_KEY_TO_H2: Dict[str, str] = {
|
|
16
|
+
"USER_PURPOSE": "Purpose",
|
|
17
|
+
"USER_QUICKSTART": "Quickstart",
|
|
18
|
+
"USER_EXAMPLES": "Examples",
|
|
19
|
+
"USER_TROUBLESHOOTING": "Troubleshooting",
|
|
20
|
+
"USER_LIMITATIONS": "Limitations",
|
|
21
|
+
"USER_RELATED_DOCS": "Related documentation",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
DEV_KEY_TO_H2: Dict[str, str] = {
|
|
25
|
+
"DEV_PREREQS": "Prerequisites",
|
|
26
|
+
"DEV_WORKFLOW": "Workflow",
|
|
27
|
+
"DEV_QUALITY_GATES": "Quality gates",
|
|
28
|
+
"DEV_ARCHITECTURE": "Architecture notes",
|
|
29
|
+
"DEV_CONTRACTS": "Contracts and interfaces",
|
|
30
|
+
"DEV_DECISIONS": "Engineering decisions",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
DEV_H2_TITLES = frozenset(DEV_KEY_TO_H2.values())
|
|
34
|
+
USER_H2_TITLES = frozenset(USER_KEY_TO_H2.values())
|
|
35
|
+
|
|
36
|
+
POINTER_H2 = "Contributing"
|
|
37
|
+
|
|
38
|
+
# Root README H2 budget (user-channel headings + optional Contributing pointer only).
|
|
39
|
+
ROOT_BUDGET: Dict[Tuple[str, str], int] = {}
|
|
40
|
+
for aud in ("user", "developer", "both"):
|
|
41
|
+
for det in ("concise", "balanced", "technical-deep"):
|
|
42
|
+
if aud == "user":
|
|
43
|
+
ROOT_BUDGET[(aud, det)] = {"concise": 5, "balanced": 7, "technical-deep": 9}[det]
|
|
44
|
+
elif aud == "developer":
|
|
45
|
+
ROOT_BUDGET[(aud, det)] = {"concise": 4, "balanced": 6, "technical-deep": 8}[det]
|
|
46
|
+
else:
|
|
47
|
+
ROOT_BUDGET[(aud, det)] = {"concise": 6, "balanced": 8, "technical-deep": 6}[det]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def resolve_doc_profile(merged: Dict[str, str]) -> Tuple[Optional[str], Optional[str], List[str]]:
|
|
51
|
+
"""Return (audience, detail, errors). Empty profile keys default per DEC-0059 §6."""
|
|
52
|
+
raw_a = (merged.get("DOC_AUDIENCE_PROFILE") or "").strip().lower()
|
|
53
|
+
raw_d = (merged.get("DOC_DETAIL_LEVEL") or "").strip().lower()
|
|
54
|
+
errors: List[str] = []
|
|
55
|
+
if raw_a and raw_a not in DOC_AUDIENCE_ALLOWED:
|
|
56
|
+
errors.append(
|
|
57
|
+
"[DOC_PROFILE_INVALID] DOC_AUDIENCE_PROFILE must be one of: "
|
|
58
|
+
f"user, developer, both (got={raw_a!r})."
|
|
59
|
+
)
|
|
60
|
+
if raw_d and raw_d not in DOC_DETAIL_ALLOWED:
|
|
61
|
+
errors.append(
|
|
62
|
+
"[DOC_PROFILE_INVALID] DOC_DETAIL_LEVEL must be one of: "
|
|
63
|
+
f"concise, balanced, technical-deep (got={raw_d!r})."
|
|
64
|
+
)
|
|
65
|
+
if errors:
|
|
66
|
+
return None, None, errors
|
|
67
|
+
audience = raw_a or "both"
|
|
68
|
+
detail = raw_d or "balanced"
|
|
69
|
+
return audience, detail, []
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def required_user_keys(audience: str, detail: str) -> Set[str]:
|
|
73
|
+
if audience not in ("user", "both"):
|
|
74
|
+
return set()
|
|
75
|
+
if detail == "concise":
|
|
76
|
+
return {"USER_PURPOSE", "USER_QUICKSTART", "USER_LIMITATIONS"}
|
|
77
|
+
if detail == "balanced":
|
|
78
|
+
return {
|
|
79
|
+
"USER_PURPOSE",
|
|
80
|
+
"USER_QUICKSTART",
|
|
81
|
+
"USER_LIMITATIONS",
|
|
82
|
+
"USER_EXAMPLES",
|
|
83
|
+
"USER_RELATED_DOCS",
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
"USER_PURPOSE",
|
|
87
|
+
"USER_QUICKSTART",
|
|
88
|
+
"USER_LIMITATIONS",
|
|
89
|
+
"USER_EXAMPLES",
|
|
90
|
+
"USER_RELATED_DOCS",
|
|
91
|
+
"USER_TROUBLESHOOTING",
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def required_dev_keys(audience: str, detail: str) -> Set[str]:
|
|
96
|
+
if audience not in ("developer", "both"):
|
|
97
|
+
return set()
|
|
98
|
+
if detail == "concise":
|
|
99
|
+
return {"DEV_PREREQS", "DEV_WORKFLOW"}
|
|
100
|
+
if detail == "balanced":
|
|
101
|
+
return {"DEV_PREREQS", "DEV_WORKFLOW", "DEV_QUALITY_GATES", "DEV_ARCHITECTURE"}
|
|
102
|
+
return {
|
|
103
|
+
"DEV_PREREQS",
|
|
104
|
+
"DEV_WORKFLOW",
|
|
105
|
+
"DEV_QUALITY_GATES",
|
|
106
|
+
"DEV_ARCHITECTURE",
|
|
107
|
+
"DEV_CONTRACTS",
|
|
108
|
+
"DEV_DECISIONS",
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def extract_h2_titles(markdown: str) -> List[str]:
|
|
113
|
+
titles: List[str] = []
|
|
114
|
+
for line in markdown.splitlines():
|
|
115
|
+
if line.startswith("## ") and not line.startswith("###"):
|
|
116
|
+
titles.append(line[3:].strip())
|
|
117
|
+
return titles
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def has_exact_h2(markdown: str, title: str) -> bool:
|
|
121
|
+
for line in markdown.splitlines():
|
|
122
|
+
if line.startswith("## ") and not line.startswith("###"):
|
|
123
|
+
if line[3:].strip() == title:
|
|
124
|
+
return True
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def count_profile_root_h2s(
|
|
129
|
+
markdown: str,
|
|
130
|
+
audience: str,
|
|
131
|
+
detail: str,
|
|
132
|
+
required_user_keys_set: Set[str],
|
|
133
|
+
) -> int:
|
|
134
|
+
"""
|
|
135
|
+
Count H2 lines in budget scope: required USER_* titles only for user/both;
|
|
136
|
+
Contributing pointer alone for developer-only (DEC-0059 / R-0054 user H2 budgets).
|
|
137
|
+
"""
|
|
138
|
+
titles = extract_h2_titles(markdown)
|
|
139
|
+
want: Set[str] = set()
|
|
140
|
+
for key in required_user_keys_set:
|
|
141
|
+
want.add(USER_KEY_TO_H2[key])
|
|
142
|
+
if audience == "developer":
|
|
143
|
+
return sum(1 for t in titles if t == POINTER_H2)
|
|
144
|
+
n = 0
|
|
145
|
+
for t in titles:
|
|
146
|
+
if t in want:
|
|
147
|
+
n += 1
|
|
148
|
+
return n
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def dev_h2_forbidden_in_root(markdown: str) -> List[str]:
|
|
152
|
+
"""Return DEV_* H2 titles present in root (split layout forbids these in README)."""
|
|
153
|
+
found: List[str] = []
|
|
154
|
+
for t in extract_h2_titles(markdown):
|
|
155
|
+
if t in DEV_H2_TITLES:
|
|
156
|
+
found.append(t)
|
|
157
|
+
return found
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def validate_optional_modes(merged: Dict[str, str], readme_text: str) -> List[str]:
|
|
161
|
+
"""
|
|
162
|
+
US-0031 / US-0032 compatibility: never require optional artifacts when modes are off.
|
|
163
|
+
When on, ensure profile surfaces mention cross-links (additive, lightweight).
|
|
164
|
+
"""
|
|
165
|
+
out: List[str] = []
|
|
166
|
+
sp = (merged.get("SPEC_PACK_MODE") or "0").strip()
|
|
167
|
+
ug = (merged.get("USER_GUIDE_MODE") or "0").strip()
|
|
168
|
+
if sp != "1" and ug != "1":
|
|
169
|
+
return out
|
|
170
|
+
if sp == "1":
|
|
171
|
+
if "docs/engineering" not in readme_text and "spec" not in readme_text.lower():
|
|
172
|
+
out.append(
|
|
173
|
+
"[DOC_OPTIONAL_CROSSLINK_WEAK] SPEC_PACK_MODE=1: root README should mention "
|
|
174
|
+
"engineering docs or spec-pack paths in a user channel section (see Related documentation)."
|
|
175
|
+
)
|
|
176
|
+
if ug == "1":
|
|
177
|
+
if "user-guides" not in readme_text and "user guide" not in readme_text.lower():
|
|
178
|
+
out.append(
|
|
179
|
+
"[DOC_OPTIONAL_CROSSLINK_WEAK] USER_GUIDE_MODE=1: root README should mention "
|
|
180
|
+
"docs/user-guides in a user channel section."
|
|
181
|
+
)
|
|
182
|
+
return out
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def ensure_section(path: str, h2_title: str, body: str) -> Tuple[bool, str]:
|
|
186
|
+
"""
|
|
187
|
+
Append ## h2_title + body if missing. Non-destructive.
|
|
188
|
+
Returns (changed, message).
|
|
189
|
+
"""
|
|
190
|
+
ensure_parent_dir(path)
|
|
191
|
+
if os.path.isfile(path):
|
|
192
|
+
text = _read_utf8(path)
|
|
193
|
+
else:
|
|
194
|
+
text = ""
|
|
195
|
+
|
|
196
|
+
if has_exact_h2(text, h2_title):
|
|
197
|
+
return False, f"[DOC_PROFILE_SYNC] skip existing: ## {h2_title} ({path})"
|
|
198
|
+
|
|
199
|
+
block = f"\n\n## {h2_title}\n\n{body.strip()}\n"
|
|
200
|
+
if not text:
|
|
201
|
+
base = os.path.basename(path)
|
|
202
|
+
if base.lower() == "readme.md":
|
|
203
|
+
text = f"# Documentation\n"
|
|
204
|
+
else:
|
|
205
|
+
text = f"# {base.replace('.md', '').replace('_', ' ')}\n"
|
|
206
|
+
new_text = text.rstrip() + block
|
|
207
|
+
_write_utf8(path, new_text)
|
|
208
|
+
return True, f"[DOC_PROFILE_SYNC] appended: ## {h2_title} ({path})"
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def ensure_doc_surfaces_merged(
|
|
212
|
+
merged: Dict[str, str],
|
|
213
|
+
target_root: str,
|
|
214
|
+
print_ok: bool = True,
|
|
215
|
+
) -> List[str]:
|
|
216
|
+
audience, detail, errors = resolve_doc_profile(merged)
|
|
217
|
+
if errors:
|
|
218
|
+
return list(errors)
|
|
219
|
+
assert audience is not None and detail is not None
|
|
220
|
+
messages: List[str] = []
|
|
221
|
+
uk = required_user_keys(audience, detail)
|
|
222
|
+
dk = required_dev_keys(audience, detail)
|
|
223
|
+
readme = os.path.join(target_root, "README.md")
|
|
224
|
+
dev_readme = os.path.join(target_root, "docs", "developer", "README.md")
|
|
225
|
+
|
|
226
|
+
stubs_user = {
|
|
227
|
+
"USER_PURPOSE": (
|
|
228
|
+
"Describe what this repository is for in plain language. "
|
|
229
|
+
"Replace this placeholder with your product outcome."
|
|
230
|
+
),
|
|
231
|
+
"USER_QUICKSTART": (
|
|
232
|
+
"Link to your fastest path to success. For its-magic, see [Setup](#setup) above."
|
|
233
|
+
),
|
|
234
|
+
"USER_EXAMPLES": "Add short, copy-paste friendly examples for common tasks.",
|
|
235
|
+
"USER_TROUBLESHOOTING": (
|
|
236
|
+
"List frequent issues, what to check, and where logs or docs live."
|
|
237
|
+
),
|
|
238
|
+
"USER_LIMITATIONS": "Call out known limits, unsupported environments, or sharp edges.",
|
|
239
|
+
"USER_RELATED_DOCS": (
|
|
240
|
+
"Link runbooks, architecture notes, and deeper guides. "
|
|
241
|
+
"Operator commands live in `docs/engineering/runbook.md`."
|
|
242
|
+
),
|
|
243
|
+
}
|
|
244
|
+
stubs_dev = {
|
|
245
|
+
"DEV_PREREQS": "Toolchain, repo layout, and local prerequisites for contributors.",
|
|
246
|
+
"DEV_WORKFLOW": "Branching, phases, and day-to-day contributor workflow.",
|
|
247
|
+
"DEV_QUALITY_GATES": "Tests, lint, typecheck, and review expectations before merge.",
|
|
248
|
+
"DEV_ARCHITECTURE": "High-level modules, boundaries, and extension points.",
|
|
249
|
+
"DEV_CONTRACTS": "Public interfaces, file formats, and compatibility promises.",
|
|
250
|
+
"DEV_DECISIONS": "Pointers to `decisions/` and architecture sections that matter.",
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
for key in sorted(uk):
|
|
254
|
+
h2 = USER_KEY_TO_H2[key]
|
|
255
|
+
changed, msg = ensure_section(readme, h2, stubs_user[key])
|
|
256
|
+
if print_ok or changed:
|
|
257
|
+
messages.append(msg)
|
|
258
|
+
|
|
259
|
+
if dk:
|
|
260
|
+
for key in sorted(dk):
|
|
261
|
+
h2 = DEV_KEY_TO_H2[key]
|
|
262
|
+
changed, msg = ensure_section(dev_readme, h2, stubs_dev[key])
|
|
263
|
+
if print_ok or changed:
|
|
264
|
+
messages.append(msg)
|
|
265
|
+
|
|
266
|
+
if audience in ("developer", "both"):
|
|
267
|
+
contrib_body = (
|
|
268
|
+
"Contributor-focused workflow and guardrails live in "
|
|
269
|
+
"[`docs/developer/README.md`](docs/developer/README.md)."
|
|
270
|
+
)
|
|
271
|
+
changed, msg = ensure_section(readme, POINTER_H2, contrib_body)
|
|
272
|
+
if print_ok or changed:
|
|
273
|
+
messages.append(msg)
|
|
274
|
+
|
|
275
|
+
return messages
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def optional_mode_warnings(merged: Dict[str, str], readme_text: str) -> List[str]:
|
|
279
|
+
"""Non-blocking hints when optional doc modes are enabled (US-0031 / US-0032)."""
|
|
280
|
+
return validate_optional_modes(merged, readme_text)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def ensure_parent_dir(path: str) -> None:
|
|
284
|
+
parent = os.path.dirname(path)
|
|
285
|
+
if parent and not os.path.isdir(parent):
|
|
286
|
+
os.makedirs(parent, exist_ok=True)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _read_utf8(path: str) -> str:
|
|
290
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
291
|
+
return f.read()
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _write_utf8(path: str, text: str) -> None:
|
|
295
|
+
ensure_parent_dir(path)
|
|
296
|
+
with open(path, "w", encoding="utf-8", newline="\n") as f:
|
|
297
|
+
f.write(text)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def validate_repo_doc_profile(
|
|
301
|
+
target_root: str,
|
|
302
|
+
merged: Dict[str, str],
|
|
303
|
+
template_root: Optional[str],
|
|
304
|
+
) -> List[str]:
|
|
305
|
+
"""
|
|
306
|
+
Full validation for active target_root; optional template_root for parity.
|
|
307
|
+
Returns error strings (empty => pass).
|
|
308
|
+
"""
|
|
309
|
+
errors: List[str] = []
|
|
310
|
+
audience, detail, err = resolve_doc_profile(merged)
|
|
311
|
+
errors.extend(err)
|
|
312
|
+
if errors:
|
|
313
|
+
return errors
|
|
314
|
+
assert audience is not None and detail is not None
|
|
315
|
+
|
|
316
|
+
uk = required_user_keys(audience, detail)
|
|
317
|
+
dk = required_dev_keys(audience, detail)
|
|
318
|
+
|
|
319
|
+
readme_path = os.path.join(target_root, "README.md")
|
|
320
|
+
dev_path = os.path.join(target_root, "docs", "developer", "README.md")
|
|
321
|
+
|
|
322
|
+
if not os.path.isfile(readme_path):
|
|
323
|
+
errors.append("[DOC_PROFILE_MERGE_ERROR] README.md missing (required for user channel checks).")
|
|
324
|
+
return errors
|
|
325
|
+
|
|
326
|
+
readme_text = _read_utf8(readme_path)
|
|
327
|
+
|
|
328
|
+
if audience in ("developer", "both") and dk:
|
|
329
|
+
if not os.path.isfile(dev_path):
|
|
330
|
+
errors.append(
|
|
331
|
+
"[DOC_SECTION_MISSING:shard] docs/developer/README.md missing but profile requires developer sections."
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
if audience in ("developer", "both"):
|
|
335
|
+
if not has_exact_h2(readme_text, POINTER_H2):
|
|
336
|
+
errors.append(
|
|
337
|
+
f"[DOC_SECTION_MISSING:DEV_SHARD_POINTER] Missing H2 ## {POINTER_H2} in README.md "
|
|
338
|
+
"(required pointer to docs/developer/README.md per DEC-0059)."
|
|
339
|
+
)
|
|
340
|
+
bad = dev_h2_forbidden_in_root(readme_text)
|
|
341
|
+
if bad:
|
|
342
|
+
errors.append(
|
|
343
|
+
"[DOC_PROFILE_INVALID] DEV sections must not use root README for split layout; "
|
|
344
|
+
f"found H2 titles {bad!r}. Move them to docs/developer/README.md."
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
for key in sorted(uk):
|
|
348
|
+
h2 = USER_KEY_TO_H2[key]
|
|
349
|
+
if not has_exact_h2(readme_text, h2):
|
|
350
|
+
errors.append(f"[DOC_SECTION_MISSING:{key}] Missing H2 ## {h2} in README.md.")
|
|
351
|
+
|
|
352
|
+
dev_text = ""
|
|
353
|
+
if os.path.isfile(dev_path):
|
|
354
|
+
dev_text = _read_utf8(dev_path)
|
|
355
|
+
|
|
356
|
+
for key in sorted(dk):
|
|
357
|
+
h2 = DEV_KEY_TO_H2[key]
|
|
358
|
+
if not has_exact_h2(dev_text, h2):
|
|
359
|
+
errors.append(f"[DOC_SECTION_MISSING:{key}] Missing H2 ## {h2} in docs/developer/README.md.")
|
|
360
|
+
|
|
361
|
+
budget = ROOT_BUDGET.get((audience, detail), 8)
|
|
362
|
+
counted = count_profile_root_h2s(readme_text, audience, detail, uk)
|
|
363
|
+
if counted > budget:
|
|
364
|
+
errors.append(
|
|
365
|
+
f"[DOC_SECTION_BUDGET_EXCEEDED] Root README profile-scoped H2 count={counted} "
|
|
366
|
+
f"exceeds budget={budget} for audience={audience!r} detail={detail!r}."
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
if template_root:
|
|
370
|
+
tr = os.path.join(template_root, "README.md")
|
|
371
|
+
td = os.path.join(template_root, "docs", "developer", "README.md")
|
|
372
|
+
if not os.path.isfile(tr):
|
|
373
|
+
errors.append("[DOC_TEMPLATE_PARITY_FAIL] template/README.md missing.")
|
|
374
|
+
else:
|
|
375
|
+
tt = _read_utf8(tr)
|
|
376
|
+
for key in sorted(uk):
|
|
377
|
+
h2 = USER_KEY_TO_H2[key]
|
|
378
|
+
if has_exact_h2(readme_text, h2) != has_exact_h2(tt, h2):
|
|
379
|
+
errors.append(
|
|
380
|
+
f"[DOC_TEMPLATE_PARITY_FAIL] USER H2 ## {h2} presence differs active vs template README."
|
|
381
|
+
)
|
|
382
|
+
if audience in ("developer", "both"):
|
|
383
|
+
if has_exact_h2(readme_text, POINTER_H2) != has_exact_h2(tt, POINTER_H2):
|
|
384
|
+
errors.append(
|
|
385
|
+
"[DOC_TEMPLATE_PARITY_FAIL] ## Contributing pointer presence differs active vs template README."
|
|
386
|
+
)
|
|
387
|
+
if dk:
|
|
388
|
+
if not os.path.isfile(td) or not os.path.isfile(dev_path):
|
|
389
|
+
errors.append(
|
|
390
|
+
"[DOC_TEMPLATE_PARITY_FAIL] developer README missing in active or template."
|
|
391
|
+
)
|
|
392
|
+
else:
|
|
393
|
+
tdev = _read_utf8(td)
|
|
394
|
+
for key in sorted(dk):
|
|
395
|
+
h2 = DEV_KEY_TO_H2[key]
|
|
396
|
+
if has_exact_h2(dev_text, h2) != has_exact_h2(tdev, h2):
|
|
397
|
+
errors.append(
|
|
398
|
+
f"[DOC_TEMPLATE_PARITY_FAIL] DEV H2 ## {h2} presence differs active vs template."
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
return errors
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def self_test_resolver() -> None:
|
|
405
|
+
"""Tier B: assert matrix key sets."""
|
|
406
|
+
assert required_user_keys("user", "concise") == {
|
|
407
|
+
"USER_PURPOSE",
|
|
408
|
+
"USER_QUICKSTART",
|
|
409
|
+
"USER_LIMITATIONS",
|
|
410
|
+
}
|
|
411
|
+
assert required_dev_keys("developer", "technical-deep") == set(DEV_KEY_TO_H2.keys())
|
|
412
|
+
a, d, e = resolve_doc_profile({"DOC_AUDIENCE_PROFILE": "", "DOC_DETAIL_LEVEL": ""})
|
|
413
|
+
assert not e and a == "both" and d == "balanced"
|
|
414
|
+
_, _, e2 = resolve_doc_profile({"DOC_AUDIENCE_PROFILE": "nope"})
|
|
415
|
+
assert e2 and "DOC_PROFILE_INVALID" in e2[0]
|
|
@@ -107,4 +107,16 @@ Mandatory intake question packs (`US-0068` / `DEC-0050`):
|
|
|
107
107
|
- `asked_topics`
|
|
108
108
|
- `missing_topics`
|
|
109
109
|
- `assumptions_confirmed`
|
|
110
|
+
- **US-0078 / DEC-0060**: also persist **`topic_coverage`** (with valid **`ie:`** refs per row),
|
|
111
|
+
keep **`asked_topics`** aligned with covered required keys, and supply
|
|
112
|
+
**`assumption_confirmation_ref`** (+ quoted text + run/turn metadata) whenever
|
|
113
|
+
`assumptions_confirmed` is affirmative or non-placeholder. Run
|
|
114
|
+
`python scripts/intake_evidence_validate.py` on the bundle **before** backlog/acceptance writes;
|
|
115
|
+
on failure emit deterministic codes (`INTAKE_REQUIRED_TOPIC_MISSING`,
|
|
116
|
+
`INTAKE_ASSUMPTION_CONFIRMATION_REQUIRED`, umbrella `INTAKE_PERSISTENCE_BLOCKED`) and **abort** writes.
|
|
117
|
+
**`INTAKE_GUIDED_MODE=0`** uses the **same** validation pipeline as guided mode.
|
|
118
|
+
- **US-0079 / DEC-0061**: for **defect** work use **`INTAKE_WORK_ITEM_KIND=bug`** and/or **`/intake bug`**;
|
|
119
|
+
run **`python scripts/intake_bug_routing_guard.py`** when allocating **`US-xxxx`** for ambiguous prose;
|
|
120
|
+
persist **`BUG-####`** under **`## Bug issues (canonical)`** with required fields and validate with
|
|
121
|
+
**`python scripts/bug_issue_validate.py --check-acceptance`**.
|
|
110
122
|
|
|
@@ -16,8 +16,8 @@ Apply narrow-read retrieval policy (US-0053):
|
|
|
16
16
|
Preferred read order:
|
|
17
17
|
1. `docs/engineering/state.md` (latest relevant checkpoint/section)
|
|
18
18
|
2. `handoffs/resume_brief.md` (current continuation intent)
|
|
19
|
-
3. `docs/product/backlog.md` (specific story block by `US-xxxx`)
|
|
20
|
-
4. `docs/product/acceptance.md` (specific checklist rows)
|
|
19
|
+
3. `docs/product/backlog.md` (specific story block by `US-xxxx` or bug block by `BUG-####` under **`## Bug issues (canonical)`**)
|
|
20
|
+
4. `docs/product/acceptance.md` (specific checklist rows, including **`## Bug acceptance (canonical)`** when answering defect status)
|
|
21
21
|
5. `docs/engineering/decisions.md` and `decisions/DEC-xxxx.md` (when decision detail is required)
|
|
22
22
|
6. `docs/engineering/architecture.md` and `docs/engineering/runbook.md` (only if implementation/policy depth is required)
|
|
23
23
|
7. `sprints/S*/progress.md` (only latest relevant sprint)
|
|
@@ -32,7 +32,7 @@ Preferred read order:
|
|
|
32
32
|
## Behavior rules
|
|
33
33
|
- Do NOT create, modify, or delete any files.
|
|
34
34
|
- Do NOT update state.md or any sprint artifacts.
|
|
35
|
-
- Reference stories (US-xxxx), decisions (DEC-xxxx), and tasks (T-xxx) by ID.
|
|
35
|
+
- Reference stories (**`US-xxxx`**), bug issues (**`BUG-####`**), decisions (**`DEC-xxxx`**), and tasks (**`T-xxx`**) by ID.
|
|
36
36
|
- Suggest next actions but do not execute them.
|
|
37
37
|
- If the question reveals a bug or feature idea, suggest running `/intake`.
|
|
38
38
|
|
|
@@ -48,9 +48,20 @@ as stale isolation evidence).
|
|
|
48
48
|
|
|
49
49
|
Release gate semantics (US-0039): mandatory gates (check-in test, QA, UAT) and no-bypass/override contract are enforced at `/release`; see `.cursor/commands/release.md` and `.cursor/commands/qa.md`.
|
|
50
50
|
|
|
51
|
+
## Intake evidence tooling reference (US-0078 / DEC-0060)
|
|
52
|
+
|
|
53
|
+
Stories that harden intake persistence ship **`scripts/intake_evidence_lib.py`**,
|
|
54
|
+
**`scripts/intake_evidence_validate.py`**, and **`tests/intake_evidence_fixtures_test.py`**
|
|
55
|
+
(invoked from **`tests/run-tests.ps1`** / **`tests/run-tests.sh`** §26k). See
|
|
56
|
+
**`docs/engineering/architecture.md`** **`# US-0078`** and **`decisions/DEC-0060.md`**.
|
|
57
|
+
|
|
58
|
+
## Bug issue tooling reference (US-0079 / DEC-0061)
|
|
59
|
+
|
|
60
|
+
Ship **`scripts/bug_issue_lib.py`**, **`scripts/bug_issue_validate.py`**, **`scripts/intake_bug_routing_guard.py`**, and **`tests/bug_issue_fixtures_test.py`** (§26L in **`tests/run-tests.ps1`** / **`tests/run-tests.sh`**). See **`docs/engineering/architecture.md`** **`# US-0079`** and **`decisions/DEC-0061.md`**.
|
|
61
|
+
|
|
51
62
|
## Canonical status contract (US-0045)
|
|
52
63
|
|
|
53
|
-
- Story status authority is `docs/product/backlog.md
|
|
64
|
+
- Story status authority is `docs/product/backlog.md` (including **`BUG-####`** under **`## Bug issues (canonical)`** per **DEC-0061**).
|
|
54
65
|
- `docs/product/acceptance.md` and `docs/engineering/state.md` are derived and
|
|
55
66
|
must not be treated as canonical readiness sources when contradictory.
|
|
56
67
|
- `/execute` must not start/continue implementation solely based on
|
|
@@ -83,6 +83,43 @@ description: "its-magic intake: clarify idea and capture story + acceptance."
|
|
|
83
83
|
- `missing_topics`
|
|
84
84
|
- `assumptions_confirmed`
|
|
85
85
|
|
|
86
|
+
## Interactive intake evidence gate (US-0078 / DEC-0060 / R-0055)
|
|
87
|
+
|
|
88
|
+
Machine-verifiable **`intake_evidence`** extends **DEC-0050** literals with
|
|
89
|
+
**`topic_coverage`** (one row per required pack key), canonical **`ie:`** refs,
|
|
90
|
+
and assumption binding. **Do not** mutate `docs/product/backlog.md` or
|
|
91
|
+
`docs/product/acceptance.md` until validation **PASS**es.
|
|
92
|
+
|
|
93
|
+
- Bundle minimum (logical shape — serialize inline in handoff/backlog notes or JSON sidecar):
|
|
94
|
+
- `selected_pack`, `asked_topics`, `missing_topics`, `assumptions_confirmed`
|
|
95
|
+
- `topic_coverage[]`: `topic_key`, `satisfied_by` (`answer_ref` \| `assumption_confirmation_ref`),
|
|
96
|
+
`ref` (**`ie:`** per **DEC-0060**), `quoted_user_text`, per-row `intake_run_id` / `turn_index`
|
|
97
|
+
(or bundle-level `intake_run_id` shared by rows)
|
|
98
|
+
- Affirmative / non-placeholder `assumptions_confirmed`: `assumption_confirmation_ref`,
|
|
99
|
+
`assumption_confirmation_intake_run_id`, `assumption_confirmation_turn_index`,
|
|
100
|
+
`assumption_confirmation_quoted`
|
|
101
|
+
- Validator (fail-closed; preserves primary sub-codes under umbrella **`INTAKE_PERSISTENCE_BLOCKED`**):
|
|
102
|
+
- `python scripts/intake_evidence_validate.py --self-test`
|
|
103
|
+
- `python scripts/intake_evidence_validate.py --file <bundle.json>` or `--stdin`
|
|
104
|
+
- Deterministic diagnostics (**AC-7**): on block, list `missing_topics`, cite reason codes, and emit
|
|
105
|
+
remediation prompts for unresolved required keys only.
|
|
106
|
+
- **Guided vs low-touch parity**: **`INTAKE_GUIDED_MODE=1`** and **`INTAKE_GUIDED_MODE=0`** run the
|
|
107
|
+
**same pre-persistence validation pipeline**; low-touch may skip optional follow-ups but **must not**
|
|
108
|
+
bypass mandatory pack evidence (**AC-5**, **AC-6**).
|
|
109
|
+
- **Grandfathering**: legacy intake rows remain valid for read/display; the **next** intake-driven
|
|
110
|
+
mutation must supply full **US-0078** evidence or the write is blocked (**DEC-0060** §5).
|
|
111
|
+
|
|
112
|
+
## Bug issue routing (US-0079 / DEC-0061)
|
|
113
|
+
|
|
114
|
+
- **Work item kind** (merged scratchpad per **DEC-0055**): **`INTAKE_WORK_ITEM_KIND=story`** (default) or **`INTAKE_WORK_ITEM_KIND=bug`**.
|
|
115
|
+
- **Explicit argv**: when the operator invokes **`/intake bug`** (bug mode for this run), treat **`INTAKE_WORK_ITEM_KIND`** as **`bug`** for routing even if scratchpad defaults to story (command wins for the session).
|
|
116
|
+
- **No silent US allocation for defects**: before creating a **`US-xxxx`** for **defect-shaped** input while in **story** kind, run **`python scripts/intake_bug_routing_guard.py --kind story --file <condensed-prose.txt>`** (or **`--stdin`**). Exit **3** → **`INTAKE_BUG_ROUTING_REQUIRED`** — **abort** backlog/acceptance mutation; remediation: set **`INTAKE_WORK_ITEM_KIND=bug`** and/or re-run as **`/intake bug`**, collect minimum bug fields, then allocate **`BUG-####`**.
|
|
117
|
+
- **Bug persistence** (after **US-0078** evidence still passes for narrative packs when applicable):
|
|
118
|
+
- Next id: **`python scripts/bug_issue_validate.py --print-next-id`** (reads **`docs/product/backlog.md`**).
|
|
119
|
+
- Append **`### BUG-#### — Title`** under **`## Bug issues (canonical)`** with **Status**, **`environment`**, **`steps_to_reproduce`**, **`expected`**, **`actual`**, **`evidence_refs`**.
|
|
120
|
+
- Add matching **`- [ ]` / `- [x]`** row under **`## Bug acceptance (canonical)`**, sorted by id.
|
|
121
|
+
- Run **`python scripts/bug_issue_validate.py --backlog docs/product/backlog.md --check-acceptance`** before completing the handoff.
|
|
122
|
+
|
|
86
123
|
## Steps
|
|
87
124
|
1. Determine intake mode from `.cursor/scratchpad.md`:
|
|
88
125
|
- guided mode: `INTAKE_GUIDED_MODE=1` (default)
|
|
@@ -129,17 +166,23 @@ description: "its-magic intake: clarify idea and capture story + acceptance."
|
|
|
129
166
|
explicitly requests depth.
|
|
130
167
|
- Keep single-story default (no forced decomposition), unless the user
|
|
131
168
|
explicitly requests decomposition.
|
|
132
|
-
5. Enforce mandatory question-pack coverage before persistence (US-0068)
|
|
169
|
+
5. Enforce mandatory question-pack coverage before persistence (US-0068) **and**
|
|
170
|
+
interactive evidence (**US-0078 / DEC-0060**):
|
|
133
171
|
- deterministically select one pack (`first-intake-pack` or
|
|
134
172
|
`small-intake-pack`) and record `selected_pack`.
|
|
135
173
|
- ask required questions for the selected pack; adaptive follow-ups remain
|
|
136
174
|
allowed but bounded.
|
|
137
|
-
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
175
|
+
- accumulate **`topic_coverage`** rows with valid **`ie:`** refs; keep
|
|
176
|
+
`asked_topics` aligned with covered required keys (asked-vs-covered rule).
|
|
177
|
+
- before writing backlog/acceptance artifacts, run **`python scripts/intake_evidence_validate.py`**
|
|
178
|
+
on the captured bundle (or equivalent in-process validation):
|
|
179
|
+
- if validation **PASS**es, proceed to persistence;
|
|
180
|
+
- on **FAIL**, emit `INTAKE_REQUIRED_TOPIC_MISSING` /
|
|
181
|
+
`INTAKE_REQUIRED_PACK_INCOMPLETE` / `INTAKE_ASSUMPTION_CONFIRMATION_REQUIRED`
|
|
182
|
+
(and umbrella `INTAKE_PERSISTENCE_BLOCKED`) with remediation guidance — **no write**.
|
|
141
183
|
- persist intake evidence fields (`asked_topics`, `missing_topics`,
|
|
142
|
-
`assumptions_confirmed`
|
|
184
|
+
`assumptions_confirmed`, `topic_coverage`, assumption ref fields per **DEC-0060**)
|
|
185
|
+
in relevant intake artifacts.
|
|
143
186
|
6. Optional fresh-project ID namespace bootstrap (US-0052 / DEC-0034):
|
|
144
187
|
- Read `ID_NAMESPACE_BOOTSTRAP` from `.cursor/scratchpad.md` (`0|1`,
|
|
145
188
|
default `0`).
|
|
@@ -36,9 +36,10 @@ description: "its-magic status-reconcile: deterministic status normalization and
|
|
|
36
36
|
- Ambiguous next OPEN story / phase resolution
|
|
37
37
|
|
|
38
38
|
## Canonical precedence (US-0045 / DEC-0025)
|
|
39
|
-
- Story status authority is `docs/product/backlog.md` only.
|
|
39
|
+
- Story status authority is `docs/product/backlog.md` only (including **`BUG-####`** under **`## Bug issues (canonical)`** per **DEC-0061** / **US-0079**).
|
|
40
40
|
- `docs/product/acceptance.md` and `docs/engineering/state.md` are derived views.
|
|
41
41
|
- Reconciliation must not infer canonical story status from derived artifacts.
|
|
42
|
+
- Bug portfolio drift vs **`## Bug acceptance (canonical)`** is machine-checkable: **`python scripts/bug_issue_validate.py --backlog docs/product/backlog.md --check-acceptance`** (**`BUG_RECONCILE_ACCEPTANCE_*`** codes).
|
|
42
43
|
|
|
43
44
|
## Deterministic detection matrix
|
|
44
45
|
1. Backlog story `Status: DONE` with unchecked AC checkboxes.
|
|
@@ -3,6 +3,12 @@ description: "Core workflow and context pack rules"
|
|
|
3
3
|
globs: ["**/*"]
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
+
- Intake backlog/acceptance writes require **US-0078** evidence validation
|
|
7
|
+
(`scripts/intake_evidence_validate.py` / **DEC-0060**) before persistence; see
|
|
8
|
+
`.cursor/commands/intake.md`.
|
|
9
|
+
- Defect intake uses **`INTAKE_WORK_ITEM_KIND=bug`** and/or **`/intake bug`**; validate bugs with
|
|
10
|
+
`scripts/bug_issue_validate.py` and run `scripts/intake_bug_routing_guard.py` before allocating
|
|
11
|
+
**`US-xxxx`** for defect-shaped prose (**US-0079** / **DEC-0061**).
|
|
6
12
|
- Use the phase flow: intake -> discovery -> research -> architecture ->
|
|
7
13
|
[security-review: design, if SECURITY_REVIEW=1] -> sprint plan -> plan verify
|
|
8
14
|
-> execute -> [security-review: code, if SECURITY_REVIEW=1] -> QA ->
|
|
@@ -124,6 +124,7 @@ AUTO_PUSH_BRANCH_ALLOWLIST=
|
|
|
124
124
|
# - INTAKE_GUIDED_MODE: 0|1 (guided intake follow-up/options/research behavior)
|
|
125
125
|
# - INTAKE_SUBAGENT_FALLBACK: deny|allow (deny by default; when deny, missing
|
|
126
126
|
# role-specific intake subagent capability fails fast)
|
|
127
|
+
# - INTAKE_WORK_ITEM_KIND: story|bug (default story; bug selects BUG-#### path per DEC-0061 / US-0079)
|
|
127
128
|
# - ID_NAMESPACE_BOOTSTRAP: 0|1 (optional fresh-project ID bootstrap mode; when 1, allow first IDs to start at 0001 only if deterministic freshness checks pass)
|
|
128
129
|
# - TOKEN_PROFILE: lean|balanced|full (tiered token-cost profile defaults)
|
|
129
130
|
# - lean: lowest-token default profile; reduce non-critical automation/research intensity
|
|
@@ -142,6 +143,7 @@ AUTO_PUSH_BRANCH_ALLOWLIST=
|
|
|
142
143
|
EARLY_RESEARCH=1
|
|
143
144
|
INTAKE_GUIDED_MODE=1
|
|
144
145
|
INTAKE_SUBAGENT_FALLBACK=deny
|
|
146
|
+
INTAKE_WORK_ITEM_KIND=story
|
|
145
147
|
ID_NAMESPACE_BOOTSTRAP=0
|
|
146
148
|
TOKEN_PROFILE=balanced
|
|
147
149
|
STATE_HOT_MAX_LINES=1200
|
|
@@ -124,6 +124,7 @@ AUTO_PUSH_BRANCH_ALLOWLIST=
|
|
|
124
124
|
# - INTAKE_GUIDED_MODE: 0|1 (guided intake follow-up/options/research behavior)
|
|
125
125
|
# - INTAKE_SUBAGENT_FALLBACK: deny|allow (deny by default; when deny, missing
|
|
126
126
|
# role-specific intake subagent capability fails fast)
|
|
127
|
+
# - INTAKE_WORK_ITEM_KIND: story|bug (default story; bug selects BUG-#### path per DEC-0061 / US-0079)
|
|
127
128
|
# - ID_NAMESPACE_BOOTSTRAP: 0|1 (optional fresh-project ID bootstrap mode; when 1, allow first IDs to start at 0001 only if deterministic freshness checks pass)
|
|
128
129
|
# - TOKEN_PROFILE: lean|balanced|full (tiered token-cost profile defaults)
|
|
129
130
|
# - lean: lowest-token default profile; reduce non-critical automation/research intensity
|
|
@@ -142,6 +143,7 @@ AUTO_PUSH_BRANCH_ALLOWLIST=
|
|
|
142
143
|
EARLY_RESEARCH=1
|
|
143
144
|
INTAKE_GUIDED_MODE=1
|
|
144
145
|
INTAKE_SUBAGENT_FALLBACK=deny
|
|
146
|
+
INTAKE_WORK_ITEM_KIND=story
|
|
145
147
|
ID_NAMESPACE_BOOTSTRAP=0
|
|
146
148
|
TOKEN_PROFILE=balanced
|
|
147
149
|
STATE_HOT_MAX_LINES=1200
|
package/template/README.md
CHANGED
|
@@ -360,6 +360,23 @@ Intake artifacts must persist coverage evidence fields:
|
|
|
360
360
|
- `missing_topics`
|
|
361
361
|
- `assumptions_confirmed`
|
|
362
362
|
|
|
363
|
+
### Interactive intake evidence + validator (US-0078 / DEC-0060)
|
|
364
|
+
|
|
365
|
+
**US-0078** closes silent persistence: every intake that mutates backlog/acceptance must pass the
|
|
366
|
+
deterministic **`intake_evidence`** gate — **`topic_coverage`** with valid **`ie:`** refs,
|
|
367
|
+
asked-vs-covered alignment, and **`assumption_confirmation_ref`** when assumptions are affirmative.
|
|
368
|
+
|
|
369
|
+
- Run `python scripts/intake_evidence_validate.py --self-test` (also exercised via `tests/run-tests.*` §26k).
|
|
370
|
+
- Operator docs: **`decisions/DEC-0060.md`**, **`docs/engineering/architecture.md`** **`# US-0078`**, runbook section **Interactive intake evidence validation (US-0078 / DEC-0060)**.
|
|
371
|
+
- **Guided** and **low-touch** share the **same pre-persistence validation pipeline**; low-touch does not bypass mandatory pack coverage.
|
|
372
|
+
|
|
373
|
+
### Bug issues + intake routing (US-0079 / DEC-0061)
|
|
374
|
+
|
|
375
|
+
Defects use **`BUG-####`** under **`docs/product/backlog.md`** **`## Bug issues (canonical)`** with **`OPEN`/`DONE`** only and minimum reproducibility fields. Intake must not silently file defect prose as **`US-xxxx`**: set merged scratchpad **`INTAKE_WORK_ITEM_KIND=bug`** and/or use **`/intake bug`**, then run **`python scripts/intake_bug_routing_guard.py --kind story --file <prose.txt>`** before story allocation when in doubt.
|
|
376
|
+
|
|
377
|
+
- Validators: `python scripts/bug_issue_validate.py --self-test`; `python scripts/bug_issue_validate.py --backlog docs/product/backlog.md --check-acceptance`.
|
|
378
|
+
- Operator docs: **`decisions/DEC-0061.md`**, **`docs/engineering/architecture.md`** **`# US-0079`**, runbook **Bug issues (US-0079 / DEC-0061)**.
|
|
379
|
+
|
|
363
380
|
### Optional ID namespace bootstrap (US-0052)
|
|
364
381
|
|
|
365
382
|
Fresh-project ID bootstrap behavior is explicit and default-off:
|
|
@@ -240,6 +240,35 @@ Required persisted intake evidence fields:
|
|
|
240
240
|
- `missing_topics`
|
|
241
241
|
- `assumptions_confirmed`
|
|
242
242
|
|
|
243
|
+
## Interactive intake evidence validation (US-0078 / DEC-0060)
|
|
244
|
+
|
|
245
|
+
**US-0078** adds machine-verifiable **`topic_coverage`** rows, canonical **`ie:`** refs
|
|
246
|
+
(**DEC-0060**), asked-vs-covered enforcement, and **`assumption_confirmation_ref`**
|
|
247
|
+
binding before backlog/acceptance writes.
|
|
248
|
+
|
|
249
|
+
- Validator entrypoints: `python scripts/intake_evidence_validate.py --self-test`;
|
|
250
|
+
`python scripts/intake_evidence_validate.py --file <bundle.json>` or `--stdin`.
|
|
251
|
+
- Library: `scripts/intake_evidence_lib.py` (shared rules for tests and tooling).
|
|
252
|
+
- Regression: `tests/intake_evidence_fixtures_test.py` (R-0055 **AC-8** matrix tiers A/B),
|
|
253
|
+
invoked from `tests/run-tests.ps1` / `tests/run-tests.sh` §26k.
|
|
254
|
+
- **Guided** and **low-touch** (`INTAKE_GUIDED_MODE=0`) share the **same** pre-persistence
|
|
255
|
+
validation pipeline; mandatory pack evidence is never skipped.
|
|
256
|
+
- Legacy intake evidence without **`ie:`** refs remains **grandfathered** for display until the
|
|
257
|
+
next intake-driven mutation, which must supply full evidence (**DEC-0060** §5).
|
|
258
|
+
|
|
259
|
+
## Bug issues (US-0079 / DEC-0061)
|
|
260
|
+
|
|
261
|
+
- **Canonical ids**: **`BUG-####`** in **`docs/product/backlog.md`** **`## Bug issues (canonical)`**; status literals **`OPEN`** | **`DONE`** only — illegal values fail **`BUG_VALIDATION_STATUS_INVALID`**.
|
|
262
|
+
- **Minimum fields** (non-empty): **`environment`**, **`steps_to_reproduce`**, **`expected`**, **`actual`**, **`evidence_refs`** — missing/empty → **`BUG_VALIDATION_FIELD_EMPTY`** (or **`BUG_VALIDATION_SECTION_MISSING`** when the region is absent).
|
|
263
|
+
- **Ordering**: bug blocks sorted by id ascending — violation → **`BUG_VALIDATION_ORDER_INVERSION`**.
|
|
264
|
+
- **Intake routing**: merged **`INTAKE_WORK_ITEM_KIND=story|bug`** and/or explicit **`/intake bug`**; defect-shaped prose with **`story`** kind → **`INTAKE_BUG_ROUTING_REQUIRED`** via **`python scripts/intake_bug_routing_guard.py`** (**DEC-0061** §5). Mismatch/conflict → **`INTAKE_WORK_ITEM_KIND_MISMATCH`** family (documented in command surfaces).
|
|
265
|
+
- **Acceptance reconciliation**: **`docs/product/acceptance.md`** **`## Bug acceptance (canonical)`** checkbox rows must match backlog bug status — drift codes **`BUG_RECONCILE_ACCEPTANCE_*`**.
|
|
266
|
+
- **Commands**:
|
|
267
|
+
- `python scripts/bug_issue_validate.py --self-test`
|
|
268
|
+
- `python scripts/bug_issue_validate.py --backlog docs/product/backlog.md [--check-acceptance] [--print-next-id]`
|
|
269
|
+
- `python scripts/intake_bug_routing_guard.py --kind story|bug --file <path>` (or **`--stdin`**)
|
|
270
|
+
- **Regression**: `tests/bug_issue_fixtures_test.py` (R-0056 Tier A/B), invoked from **`tests/run-tests.ps1` / `tests/run-tests.sh`** §26L.
|
|
271
|
+
|
|
243
272
|
## Optional ID namespace bootstrap (US-0052)
|
|
244
273
|
|
|
245
274
|
Fresh-project ID bootstrap is optional and default-off in
|
|
@@ -1 +1,5 @@
|
|
|
1
1
|
# Backlog
|
|
2
|
+
|
|
3
|
+
## Bug issues (canonical)
|
|
4
|
+
|
|
5
|
+
Stub — materialized installs use the full **`## Bug issues (canonical)`** contract in **`docs/product/backlog.md`** (**US-0079** / **DEC-0061**). Append **`### BUG-#### — Title`** blocks here when tracking defects; run **`python scripts/bug_issue_validate.py`** before handoff.
|