its-magic 0.1.2-37 → 0.1.2-39
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/installer.ps1 +20 -0
- package/installer.py +66 -2
- package/installer.sh +22 -0
- package/package.json +2 -1
- package/scripts/check_intake_template_parity.py +1 -0
- package/scripts/intake_evidence_lib.py +413 -10
- package/scripts/intake_evidence_validate.py +2 -2
- package/scripts/materialize_codebase_map.py +184 -0
- package/template/.cursor/agents/po.mdc +19 -0
- package/template/.cursor/commands/architecture.md +12 -0
- package/template/.cursor/commands/ask.md +11 -0
- package/template/.cursor/commands/auto.md +20 -2
- package/template/.cursor/commands/intake.md +64 -9
- package/template/.cursor/commands/map-codebase.md +18 -1
- package/template/.cursor/commands/refresh-context.md +7 -0
- package/template/.cursor/rules/core.mdc +5 -0
- package/template/docs/engineering/artifact-ownership-policy.md +1 -1
- package/template/docs/engineering/context/installer-owned-paths.manifest +17 -0
- package/template/docs/engineering/runbook.md +76 -2
- package/template/scripts/check_intake_template_parity.py +1 -0
- package/template/scripts/enforce-triad-hot-surface.py +626 -0
- package/template/scripts/intake_bug_resume_brief_refresh.py +303 -0
- package/template/scripts/intake_evidence_lib.py +413 -10
- package/template/scripts/intake_evidence_validate.py +2 -2
- package/template/scripts/materialize_codebase_map.py +184 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Atomic refresh of handoffs/resume_brief.md after successful /intake bug persistence (DEC-0069 / BUG-0005).
|
|
4
|
+
|
|
5
|
+
Idempotent: same inputs yield the same latest-pointer section. Uses temp file + os.replace for atomicity.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import sys
|
|
14
|
+
import tempfile
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
import bug_issue_lib as bi
|
|
18
|
+
import bug_issue_validate as biv
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def bug_status(backlog_text: str, bug_id: str) -> str | None:
|
|
22
|
+
section = bi.extract_bug_section(backlog_text)
|
|
23
|
+
if not section:
|
|
24
|
+
return None
|
|
25
|
+
for issue in bi.parse_bug_issues(section):
|
|
26
|
+
if issue.bug_id == bug_id:
|
|
27
|
+
return issue.status
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def upsert_latest_orchestration_pointer(full_text: str, latest_block: str) -> str:
|
|
32
|
+
"""Replace ## Latest orchestration pointer section or insert after # Resume Brief."""
|
|
33
|
+
latest_block = latest_block.rstrip("\n") + "\n"
|
|
34
|
+
lines = full_text.splitlines(keepends=True)
|
|
35
|
+
if not lines:
|
|
36
|
+
return "# Resume Brief\n\n" + latest_block
|
|
37
|
+
|
|
38
|
+
out: list[str] = []
|
|
39
|
+
i = 0
|
|
40
|
+
replaced = False
|
|
41
|
+
while i < len(lines):
|
|
42
|
+
line = lines[i]
|
|
43
|
+
if line.startswith("## Latest orchestration pointer"):
|
|
44
|
+
out.append(latest_block)
|
|
45
|
+
i += 1
|
|
46
|
+
while i < len(lines) and not lines[i].startswith("## "):
|
|
47
|
+
i += 1
|
|
48
|
+
replaced = True
|
|
49
|
+
continue
|
|
50
|
+
out.append(line)
|
|
51
|
+
i += 1
|
|
52
|
+
|
|
53
|
+
if replaced:
|
|
54
|
+
return "".join(out)
|
|
55
|
+
|
|
56
|
+
text = "".join(lines)
|
|
57
|
+
stripped = text.lstrip("\n")
|
|
58
|
+
if stripped.startswith("# Resume Brief"):
|
|
59
|
+
return text.rstrip("\n") + "\n\n" + latest_block
|
|
60
|
+
return "# Resume Brief\n\n" + latest_block + text.lstrip("\n")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def build_latest_pointer_markdown(
|
|
64
|
+
*,
|
|
65
|
+
bug_id: str,
|
|
66
|
+
intake_boundary_utc: str,
|
|
67
|
+
orchestrator_run_id: str | None,
|
|
68
|
+
intake_evidence_ref: str | None,
|
|
69
|
+
sprint_id: str | None,
|
|
70
|
+
) -> str:
|
|
71
|
+
orch = orchestrator_run_id if orchestrator_run_id else "(unknown)"
|
|
72
|
+
ev = intake_evidence_ref if intake_evidence_ref else "(none)"
|
|
73
|
+
spr = sprint_id if sprint_id else "(none)"
|
|
74
|
+
return f"""## Latest orchestration pointer — post-bug-intake (DEC-0069)
|
|
75
|
+
|
|
76
|
+
- **Boundary**: successful **`/intake bug`** persistence (**`US-0045`**) — **`intake_boundary_utc={intake_boundary_utc}`**
|
|
77
|
+
- **`bug_id`**: **`{bug_id}`** — must remain **`OPEN`** in **`docs/product/backlog.md`** (authority); this refresh is rejected if backlog shows **DONE**
|
|
78
|
+
- **Intake evidence ref**: `{ev}`
|
|
79
|
+
- **`orchestrator_run_id`**: `{orch}` (boundary metadata when known; optional at intake)
|
|
80
|
+
- **Contract**: default **`/auto`** continuation targets **`discovery`** for this OPEN bug (not a stale pre-intake **`intake`** resume target)
|
|
81
|
+
|
|
82
|
+
## Current status
|
|
83
|
+
|
|
84
|
+
- **Active bug**: **`{bug_id}`** — **OPEN** per **`docs/product/backlog.md`** at refresh time
|
|
85
|
+
|
|
86
|
+
## Intended resume phase
|
|
87
|
+
|
|
88
|
+
`discovery`
|
|
89
|
+
|
|
90
|
+
## Resume target
|
|
91
|
+
|
|
92
|
+
- bug_id={bug_id}
|
|
93
|
+
- story_id=(none)
|
|
94
|
+
- sprint_id={spr}
|
|
95
|
+
- boundary=post-bug-intake (**DEC-0069**)
|
|
96
|
+
|
|
97
|
+
## Latest auto breadcrumb seed
|
|
98
|
+
|
|
99
|
+
- requested_start_from=(none)
|
|
100
|
+
- resolved_start_phase=discovery
|
|
101
|
+
- resolution_source=resume_brief
|
|
102
|
+
- resolution_status=resolved
|
|
103
|
+
- stop_reason=intake_complete
|
|
104
|
+
- stop_phase=intake
|
|
105
|
+
- next_scheduled_phase=discovery
|
|
106
|
+
- bug_id={bug_id}
|
|
107
|
+
- story_id=(none)
|
|
108
|
+
- sprint_id={spr}
|
|
109
|
+
- orchestrator_run_id={orch}
|
|
110
|
+
- intake_boundary_utc={intake_boundary_utc}
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def extract_brief_bug_id(brief_text: str) -> str | None:
|
|
115
|
+
for line in brief_text.splitlines():
|
|
116
|
+
s = line.strip()
|
|
117
|
+
m = re.match(r"^-\s*bug_id=(BUG-\d{4})\s*$", s)
|
|
118
|
+
if m:
|
|
119
|
+
return m.group(1)
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def validate_brief_open_bug_alignment(brief_text: str, backlog_text: str) -> list[str]:
|
|
124
|
+
"""Writer-side guard: brief bug_id must match an OPEN row in backlog (US-0045)."""
|
|
125
|
+
errors: list[str] = []
|
|
126
|
+
bid = extract_brief_bug_id(brief_text)
|
|
127
|
+
if not bid:
|
|
128
|
+
errors.append("INTAKE_RESUME_BRIEF_PARSE_BUG_ID_MISSING")
|
|
129
|
+
return errors
|
|
130
|
+
|
|
131
|
+
st = bug_status(backlog_text, bid)
|
|
132
|
+
if st is None:
|
|
133
|
+
errors.append(f"INTAKE_RESUME_BRIEF_BACKLOG_BUG_UNKNOWN:{bid}")
|
|
134
|
+
return errors
|
|
135
|
+
if st != "OPEN":
|
|
136
|
+
errors.append(f"INTAKE_RESUME_BRIEF_BACKLOG_CONTRADICTION:{bid}:status={st}")
|
|
137
|
+
if "`discovery`" not in brief_text and "resolved_start_phase=discovery" not in brief_text:
|
|
138
|
+
errors.append("INTAKE_RESUME_BRIEF_DISCOVERY_PHASE_MISSING")
|
|
139
|
+
return errors
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def atomic_write(path: Path, content: str) -> None:
|
|
143
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
144
|
+
fd, tmp = tempfile.mkstemp(
|
|
145
|
+
dir=path.parent,
|
|
146
|
+
prefix=".resume_brief_tmp_",
|
|
147
|
+
suffix=".md",
|
|
148
|
+
)
|
|
149
|
+
try:
|
|
150
|
+
with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as f:
|
|
151
|
+
f.write(content)
|
|
152
|
+
os.replace(tmp, path)
|
|
153
|
+
except Exception:
|
|
154
|
+
try:
|
|
155
|
+
os.unlink(tmp)
|
|
156
|
+
except OSError:
|
|
157
|
+
pass
|
|
158
|
+
raise
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _fail(code: str, detail: str = "") -> int:
|
|
162
|
+
msg = code
|
|
163
|
+
if detail:
|
|
164
|
+
msg += f": {detail}"
|
|
165
|
+
print(msg, file=sys.stderr)
|
|
166
|
+
return 1
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def self_test() -> int:
|
|
170
|
+
block = build_latest_pointer_markdown(
|
|
171
|
+
bug_id="BUG-0999",
|
|
172
|
+
intake_boundary_utc="2026-04-03T12:00:00Z",
|
|
173
|
+
orchestrator_run_id="auto-test",
|
|
174
|
+
intake_evidence_ref="handoffs/intake_evidence/x.json",
|
|
175
|
+
sprint_id="S0001",
|
|
176
|
+
)
|
|
177
|
+
old = "# Resume Brief\n\n## Latest orchestration pointer — old\n\n- x\n\n## Checkpoint\n\nkeep\n"
|
|
178
|
+
new = upsert_latest_orchestration_pointer(old, block)
|
|
179
|
+
if "keep" not in new:
|
|
180
|
+
return _fail("SELFTEST_FAILED", "lost tail section")
|
|
181
|
+
if "BUG-0999" not in new or "resolved_start_phase=discovery" not in new:
|
|
182
|
+
return _fail("SELFTEST_FAILED", "missing expected content")
|
|
183
|
+
if "## Latest orchestration pointer — old" in new:
|
|
184
|
+
return _fail("SELFTEST_FAILED", "old latest not replaced")
|
|
185
|
+
good_backlog = """## Bug issues (canonical)
|
|
186
|
+
|
|
187
|
+
### BUG-0999 — T
|
|
188
|
+
- Status: OPEN
|
|
189
|
+
- environment: e
|
|
190
|
+
- steps_to_reproduce: s
|
|
191
|
+
- expected: x
|
|
192
|
+
- actual: y
|
|
193
|
+
- evidence_refs: z
|
|
194
|
+
"""
|
|
195
|
+
errs = validate_brief_open_bug_alignment(new, good_backlog)
|
|
196
|
+
if errs:
|
|
197
|
+
return _fail("SELFTEST_FAILED", str(errs))
|
|
198
|
+
bad_backlog = good_backlog.replace("OPEN", "DONE")
|
|
199
|
+
errs2 = validate_brief_open_bug_alignment(new, bad_backlog)
|
|
200
|
+
if not any("CONTRADICTION" in e for e in errs2):
|
|
201
|
+
return _fail("SELFTEST_FAILED", "expected contradiction on DONE")
|
|
202
|
+
print("[INTAKE_BUG_RESUME_BRIEF_REFRESH_OK]")
|
|
203
|
+
return 0
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def main() -> int:
|
|
207
|
+
ap = argparse.ArgumentParser(description=__doc__)
|
|
208
|
+
ap.add_argument("--bug-id", default="", help="Persisted BUG-#### id (required unless --self-test)")
|
|
209
|
+
ap.add_argument("--backlog", default="docs/product/backlog.md")
|
|
210
|
+
ap.add_argument("--resume-brief", default="handoffs/resume_brief.md")
|
|
211
|
+
ap.add_argument(
|
|
212
|
+
"--intake-boundary-utc",
|
|
213
|
+
default="",
|
|
214
|
+
help="RFC3339 UTC timestamp for intake completion boundary (required unless --validate-file)",
|
|
215
|
+
)
|
|
216
|
+
ap.add_argument("--orchestrator-run-id", default="", help="Optional orchestrator run id")
|
|
217
|
+
ap.add_argument("--intake-evidence", default="", help="Optional intake evidence path or ref")
|
|
218
|
+
ap.add_argument("--sprint-id", default="", help="Optional sprint id")
|
|
219
|
+
ap.add_argument("--dry-run", action="store_true", help="Print body only; do not write")
|
|
220
|
+
ap.add_argument("--validate-file", action="store_true", help="Validate existing brief vs backlog; no write")
|
|
221
|
+
ap.add_argument("--self-test", action="store_true")
|
|
222
|
+
args = ap.parse_args()
|
|
223
|
+
if args.self_test:
|
|
224
|
+
return self_test()
|
|
225
|
+
|
|
226
|
+
if not args.bug_id:
|
|
227
|
+
return _fail("INTAKE_RESUME_BRIEF_INVALID_BUG_ID", "missing --bug-id")
|
|
228
|
+
if not re.fullmatch(r"BUG-\d{4}", args.bug_id):
|
|
229
|
+
return _fail("INTAKE_RESUME_BRIEF_INVALID_BUG_ID", args.bug_id)
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
backlog_text = Path(args.backlog).read_text(encoding="utf-8")
|
|
233
|
+
except OSError as e:
|
|
234
|
+
return _fail("INTAKE_RESUME_BRIEF_IO_ERROR", str(e))
|
|
235
|
+
|
|
236
|
+
berr, _ = biv.validate_backlog(backlog_text)
|
|
237
|
+
if berr:
|
|
238
|
+
for e in berr:
|
|
239
|
+
print(e, file=sys.stderr)
|
|
240
|
+
return _fail("INTAKE_RESUME_BRIEF_BACKLOG_INVALID", berr[0])
|
|
241
|
+
|
|
242
|
+
resume_path = Path(args.resume_brief)
|
|
243
|
+
if args.validate_file:
|
|
244
|
+
if not resume_path.is_file():
|
|
245
|
+
return _fail("INTAKE_RESUME_BRIEF_MISSING", str(resume_path))
|
|
246
|
+
brief_text = resume_path.read_text(encoding="utf-8")
|
|
247
|
+
bid_file = extract_brief_bug_id(brief_text)
|
|
248
|
+
if bid_file != args.bug_id:
|
|
249
|
+
return _fail(
|
|
250
|
+
"INTAKE_RESUME_BRIEF_BUG_ID_MISMATCH",
|
|
251
|
+
f"cli={args.bug_id} brief={bid_file}",
|
|
252
|
+
)
|
|
253
|
+
verr = validate_brief_open_bug_alignment(brief_text, backlog_text)
|
|
254
|
+
if verr:
|
|
255
|
+
for e in verr:
|
|
256
|
+
print(e, file=sys.stderr)
|
|
257
|
+
return 1
|
|
258
|
+
print("[INTAKE_RESUME_BRIEF_VALIDATE_OK]")
|
|
259
|
+
return 0
|
|
260
|
+
|
|
261
|
+
if not args.intake_boundary_utc.strip():
|
|
262
|
+
return _fail("INTAKE_RESUME_BRIEF_BOUNDARY_UTC_REQUIRED", "supply --intake-boundary-utc")
|
|
263
|
+
|
|
264
|
+
st = bug_status(backlog_text, args.bug_id)
|
|
265
|
+
if st is None:
|
|
266
|
+
return _fail("INTAKE_RESUME_BRIEF_BUG_NOT_FOUND", args.bug_id)
|
|
267
|
+
if st != "OPEN":
|
|
268
|
+
return _fail(
|
|
269
|
+
"INTAKE_RESUME_BRIEF_BACKLOG_CONTRADICTION",
|
|
270
|
+
f"{args.bug_id} must be OPEN for discovery default; got {st}",
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
block = build_latest_pointer_markdown(
|
|
274
|
+
bug_id=args.bug_id,
|
|
275
|
+
intake_boundary_utc=args.intake_boundary_utc,
|
|
276
|
+
orchestrator_run_id=args.orchestrator_run_id or None,
|
|
277
|
+
intake_evidence_ref=args.intake_evidence or None,
|
|
278
|
+
sprint_id=args.sprint_id or None,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
prior = resume_path.read_text(encoding="utf-8") if resume_path.is_file() else ""
|
|
282
|
+
merged = upsert_latest_orchestration_pointer(prior, block)
|
|
283
|
+
verr = validate_brief_open_bug_alignment(merged, backlog_text)
|
|
284
|
+
if verr:
|
|
285
|
+
for e in verr:
|
|
286
|
+
print(e, file=sys.stderr)
|
|
287
|
+
return 1
|
|
288
|
+
|
|
289
|
+
if args.dry_run:
|
|
290
|
+
print(merged)
|
|
291
|
+
return 0
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
atomic_write(resume_path, merged)
|
|
295
|
+
except OSError as e:
|
|
296
|
+
return _fail("INTAKE_RESUME_BRIEF_WRITE_FAILED", str(e))
|
|
297
|
+
|
|
298
|
+
print("[INTAKE_BUG_RESUME_BRIEF_REFRESH_OK]")
|
|
299
|
+
return 0
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
if __name__ == "__main__":
|
|
303
|
+
raise SystemExit(main())
|