its-magic 0.1.2-9 → 0.1.3-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/README.md +1602 -755
- package/bin/its-magic.js +121 -11
- package/bin/postinstall.js +93 -0
- package/installer.ps1 +759 -0
- package/installer.py +1036 -0
- package/installer.sh +649 -0
- package/package.json +18 -14
- package/scripts/check_intake_template_parity.py +312 -0
- package/scripts/doc_profile_lib.py +415 -0
- package/scripts/guard_installer_publish.py +88 -0
- package/scripts/intake_bug_routing_guard.py +67 -0
- package/scripts/intake_evidence_lib.py +802 -0
- package/scripts/intake_evidence_validate.py +73 -0
- package/scripts/materialize_codebase_map.py +184 -0
- package/scripts/remote_config_summary.py +243 -0
- package/template/.cursor/agents/curator.mdc +35 -0
- package/template/.cursor/agents/dev.mdc +29 -0
- package/template/.cursor/agents/po.mdc +141 -0
- package/template/.cursor/agents/qa.mdc +28 -0
- package/template/.cursor/agents/release.mdc +28 -0
- package/template/.cursor/agents/security.mdc +98 -0
- package/template/.cursor/agents/tech-lead.mdc +57 -0
- package/template/.cursor/commands/architecture.md +127 -0
- package/template/.cursor/commands/ask.md +55 -0
- package/template/.cursor/commands/auto.md +533 -0
- package/template/.cursor/commands/discovery.md +47 -0
- package/template/.cursor/commands/execute.md +343 -0
- package/template/.cursor/commands/intake.md +323 -0
- package/template/.cursor/commands/map-codebase.md +46 -0
- package/template/.cursor/commands/memory-audit.md +175 -0
- package/template/.cursor/commands/milestone-complete.md +51 -0
- package/template/.cursor/commands/milestone-start.md +59 -0
- package/template/.cursor/commands/pause.md +64 -0
- package/{.cursor/commands/gsd-phase-context.md → template/.cursor/commands/phase-context.md} +6 -2
- package/template/.cursor/commands/plan-verify.md +34 -0
- package/template/.cursor/commands/qa.md +226 -0
- package/template/.cursor/commands/quick.md +41 -0
- package/template/.cursor/commands/refresh-context.md +82 -0
- package/template/.cursor/commands/release.md +523 -0
- package/template/.cursor/commands/research.md +49 -0
- package/template/.cursor/commands/resume.md +67 -0
- package/template/.cursor/commands/security-review.md +81 -0
- package/template/.cursor/commands/sprint-plan.md +103 -0
- package/template/.cursor/commands/status-reconcile.md +95 -0
- package/template/.cursor/commands/verify-work.md +152 -0
- package/template/.cursor/dev-environment.json.example +22 -0
- package/{.cursor → template/.cursor}/hooks/README.md +3 -2
- package/{.cursor/hooks/gsd-hook.py → template/.cursor/hooks/hook.py} +15 -6
- package/template/.cursor/hooks.json +26 -0
- package/template/.cursor/remote.json +31 -0
- package/template/.cursor/rules/caveman.mdc +184 -0
- package/template/.cursor/rules/coding-standards.mdc +39 -0
- package/template/.cursor/rules/core.mdc +111 -0
- package/template/.cursor/rules/handoffs.mdc +27 -0
- package/template/.cursor/rules/quality.mdc +49 -0
- package/template/.cursor/scratchpad.local.example.md +323 -0
- package/template/.cursor/scratchpad.md +324 -0
- package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/SKILL.md +2 -2
- package/template/.cursorignore +6 -0
- package/template/.env.example +28 -0
- package/template/.github/workflows/ci.yml +194 -0
- package/{.github → template/.github}/workflows/deploy.yml +5 -2
- package/template/.its-magic-version +1 -0
- package/template/CHANGELOG.md +11 -0
- package/template/README.md +1602 -0
- package/template/decisions/DEC-0001.md +11 -0
- package/template/decisions/DEC-0002.md +11 -0
- package/template/docs/developer/README.md +44 -0
- package/template/docs/engineering/architecture.md +9 -0
- package/template/docs/engineering/artifact-ordering-policy.md +48 -0
- package/template/docs/engineering/artifact-ownership-policy.md +57 -0
- package/template/docs/engineering/auto-orchestration-reference.md +1211 -0
- package/{docs → template/docs}/engineering/codebase-map.md +0 -5
- package/template/docs/engineering/compatibility-report.md +20 -0
- package/template/docs/engineering/compatibility-signals.md +7 -0
- package/template/docs/engineering/component-scope-report.md +16 -0
- package/template/docs/engineering/component-scope.md +19 -0
- package/template/docs/engineering/context/installer-owned-paths.manifest +96 -0
- package/template/docs/engineering/context/readme-section-affinity.json +30 -0
- package/template/docs/engineering/decisions.md +16 -0
- package/template/docs/engineering/legacy-drift-audit.md +21 -0
- package/template/docs/engineering/manifests/components/api-gateway.manifest.yaml +12 -0
- package/template/docs/engineering/manifests/registry.manifest.yaml +20 -0
- package/template/docs/engineering/manifests/repo.manifest.yaml +11 -0
- package/template/docs/engineering/phase-context.md +16 -0
- package/template/docs/engineering/release-targets.json +123 -0
- package/template/docs/engineering/research.md +29 -0
- package/template/docs/engineering/runbook.md +2391 -0
- package/template/docs/engineering/runtime-connectivity.md +81 -0
- package/template/docs/engineering/security-review.md +33 -0
- package/template/docs/engineering/spec-pack/README.md +20 -0
- package/template/docs/engineering/state-archive/README.md +15 -0
- package/template/docs/engineering/state.md +20 -0
- package/template/docs/engineering/status-normalization-report.md +19 -0
- package/template/docs/engineering/token-cost-parity-manifest.md +16 -0
- package/template/docs/engineering/us-0084-remote-e2e.md +44 -0
- package/template/docs/product/acceptance.md +1 -0
- package/template/docs/product/backlog.md +5 -0
- package/template/docs/product/vision.md +11 -0
- package/template/docs/user-guides/README.md +40 -0
- package/{handoffs → template/handoffs}/dev_to_qa.md +0 -3
- package/{handoffs → template/handoffs}/po_to_tl.md +0 -3
- package/{handoffs → template/handoffs}/qa_to_dev.md +1 -2
- package/template/handoffs/release_notes.md +51 -0
- package/template/handoffs/release_queue.md +47 -0
- package/template/handoffs/release_to_dev.md +31 -0
- package/template/handoffs/releases/Sxxxx-release-notes.md +62 -0
- package/template/handoffs/releases/vX.Y.Z-release-notes.md.example +21 -0
- package/template/handoffs/resume_brief.md +14 -0
- package/{handoffs → template/handoffs}/tl_to_dev.md +0 -2
- package/template/handoffs/token_cost_runs/README.md +26 -0
- package/template/its_magic/.its-magic-version +1 -0
- package/template/its_magic/README.md +9 -0
- package/template/scripts/auto_outer_driver.py +521 -0
- package/template/scripts/caveman_compress_input.py +903 -0
- package/template/scripts/check_downstream_ci_guard.py +67 -0
- package/template/scripts/check_intake_template_parity.py +312 -0
- package/template/scripts/check_token_cost_parity.py +69 -0
- package/template/scripts/dev_environment_lib.py +601 -0
- package/template/scripts/doc_profile_lib.py +415 -0
- package/template/scripts/downstream_ci_guard_lib.py +222 -0
- package/template/scripts/enforce-triad-hot-surface.py +753 -0
- package/template/scripts/guard_installer_publish.py +88 -0
- package/template/scripts/intake_bug_resume_brief_refresh.py +303 -0
- package/template/scripts/intake_bug_routing_guard.py +67 -0
- package/template/scripts/intake_evidence_lib.py +802 -0
- package/template/scripts/intake_evidence_validate.py +73 -0
- package/template/scripts/materialize_codebase_map.py +184 -0
- package/template/scripts/pack_json_validate.py +130 -0
- package/template/scripts/project_readme_coverage_lib.py +417 -0
- package/template/scripts/readme_feature_coverage_lib.py +608 -0
- package/{scripts → template/scripts}/release-all.sh +27 -4
- package/template/scripts/release_changelog_backfill.py +153 -0
- package/template/scripts/release_changelog_lib.py +544 -0
- package/template/scripts/release_changelog_validate.py +134 -0
- package/template/scripts/remote_config_summary.py +243 -0
- package/template/scripts/sync_push_gates.py +198 -0
- package/template/scripts/token_cost_compare.py +40 -0
- package/template/scripts/token_cost_lib.py +108 -0
- package/template/scripts/uat_probe_lib.py +868 -0
- package/template/scripts/validate-and-push.ps1 +280 -0
- package/template/scripts/validate-and-push.sh +243 -0
- package/template/scripts/validate_doc_profile.py +103 -0
- package/template/scripts/validate_project_readme_coverage.py +151 -0
- package/template/scripts/validate_readme_feature_coverage.py +140 -0
- package/template/sprints/S0001/progress.md +1 -0
- package/template/sprints/S0001/qa-findings.md +9 -0
- package/template/sprints/S0001/release-findings.md +24 -0
- package/template/sprints/S0001/sprint.md +9 -0
- package/template/sprints/S0001/summary.md +7 -0
- package/template/sprints/S0001/tasks.md +1 -0
- package/template/sprints/S0001/uat.json +6 -0
- package/template/sprints/S0001/uat.md +5 -0
- package/template/sprints/quick/Q0001/summary.md +1 -0
- package/{sprints → template/sprints}/quick/Q0001/task.json +0 -1
- package/.cursor/agents/curator.mdc +0 -21
- package/.cursor/agents/dev.mdc +0 -20
- package/.cursor/agents/po.mdc +0 -19
- package/.cursor/agents/qa.mdc +0 -19
- package/.cursor/agents/release.mdc +0 -19
- package/.cursor/agents/tech-lead.mdc +0 -21
- package/.cursor/commands/gsd-architecture.md +0 -29
- package/.cursor/commands/gsd-auto.md +0 -27
- package/.cursor/commands/gsd-discovery.md +0 -27
- package/.cursor/commands/gsd-execute.md +0 -32
- package/.cursor/commands/gsd-intake.md +0 -28
- package/.cursor/commands/gsd-map-codebase.md +0 -25
- package/.cursor/commands/gsd-milestone-complete.md +0 -24
- package/.cursor/commands/gsd-milestone-start.md +0 -26
- package/.cursor/commands/gsd-pause.md +0 -25
- package/.cursor/commands/gsd-plan-verify.md +0 -26
- package/.cursor/commands/gsd-qa.md +0 -28
- package/.cursor/commands/gsd-quick.md +0 -24
- package/.cursor/commands/gsd-refresh-context.md +0 -26
- package/.cursor/commands/gsd-release.md +0 -29
- package/.cursor/commands/gsd-research.md +0 -28
- package/.cursor/commands/gsd-resume.md +0 -26
- package/.cursor/commands/gsd-sprint-plan.md +0 -30
- package/.cursor/commands/gsd-verify-work.md +0 -25
- package/.cursor/hooks.json +0 -26
- package/.cursor/plans/cursor-gsd-team-kit_8cfee9b8.plan.md +0 -57
- package/.cursor/remote.json +0 -18
- package/.cursor/rules/gsd-core.mdc +0 -18
- package/.cursor/rules/gsd-handoffs.mdc +0 -10
- package/.cursor/rules/gsd-quality.mdc +0 -15
- package/.cursor/scratchpad.md +0 -34
- package/.github/workflows/ci.yml +0 -47
- package/decisions/DEC-0001.md +0 -21
- package/decisions/DEC-0002.md +0 -21
- package/docs/engineering/architecture.md +0 -354
- package/docs/engineering/decisions.md +0 -6
- package/docs/engineering/research.md +0 -11
- package/docs/engineering/runbook.md +0 -32
- package/docs/engineering/state.md +0 -33
- package/docs/product/acceptance.md +0 -6
- package/docs/product/backlog.md +0 -7
- package/docs/product/vision.md +0 -46
- package/gsd-installer.ps1 +0 -189
- package/gsd-installer.py +0 -195
- package/gsd-installer.sh +0 -201
- package/handoffs/release_notes.md +0 -14
- package/handoffs/resume_brief.md +0 -8
- package/milestones/M0001/milestone.json +0 -7
- package/milestones/M0001/phases.json +0 -9
- package/milestones/M0001/progress.md +0 -3
- package/milestones/M0001/summary.md +0 -3
- package/scripts/generate-release-notes.ps1 +0 -74
- package/scripts/generate-release-notes.sh +0 -63
- package/scripts/release-all.ps1 +0 -423
- package/sprints/S0001/progress.md +0 -4
- package/sprints/S0001/qa-findings.md +0 -113
- package/sprints/S0001/sprint.md +0 -70
- package/sprints/S0001/summary.md +0 -46
- package/sprints/S0001/tasks.md +0 -35
- package/sprints/S0001/uat.json +0 -8
- package/sprints/S0001/uat.md +0 -8
- package/sprints/quick/Q0001/summary.md +0 -3
- /package/{.cursor/rules/gsd-escalation.mdc → template/.cursor/rules/escalation.mdc} +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/acceptance.json +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/acceptance.md +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/architecture.json +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/architecture.md +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/decision.json +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/decision.md +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/handoff.json +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/handoff.md +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/milestone.json +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/phase-context.json +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/plan-verify.json +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/sprint.json +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/sprint.md +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/story.json +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/story.md +0 -0
- /package/{.cursor/skills/gsd-team → template/.cursor/skills/its-magic}/templates/uat.json +0 -0
- /package/{docs → template/docs}/engineering/context/phase-template.json +0 -0
- /package/{docs → template/docs}/engineering/dependencies.json +0 -0
- /package/{sprints → template/sprints}/S0001/plan-verify.json +0 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Prepublish / CI guard: installer.sh LF + POSIX-safe startup tokens (US-0084 / AC-2).
|
|
3
|
+
|
|
4
|
+
BUG-0008: reject CR bytes in installer-owned-paths.manifest (CRLF breaks POSIX awk section match).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
15
|
+
INSTALLER_SH = ROOT / "installer.sh"
|
|
16
|
+
INSTALLER_MANIFESTS = (
|
|
17
|
+
ROOT / "docs" / "engineering" / "context" / "installer-owned-paths.manifest",
|
|
18
|
+
ROOT / "template" / "docs" / "engineering" / "context" / "installer-owned-paths.manifest",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
FORBIDDEN_TOKENS = (
|
|
22
|
+
"set -euo",
|
|
23
|
+
"set -o pipefail",
|
|
24
|
+
"set -eu -o pipefail",
|
|
25
|
+
"set -o errexit",
|
|
26
|
+
"set -o nounset",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def main() -> int:
|
|
31
|
+
if not INSTALLER_SH.is_file():
|
|
32
|
+
print("guard_installer_publish: installer.sh missing", file=sys.stderr)
|
|
33
|
+
return 1
|
|
34
|
+
data = INSTALLER_SH.read_bytes()
|
|
35
|
+
if b"\r" in data:
|
|
36
|
+
print(
|
|
37
|
+
"guard_installer_publish: CR/LF (\\r) bytes found in installer.sh — "
|
|
38
|
+
"use LF only; see docs/engineering/runbook.md (US-0084).",
|
|
39
|
+
file=sys.stderr,
|
|
40
|
+
)
|
|
41
|
+
return 1
|
|
42
|
+
for man in INSTALLER_MANIFESTS:
|
|
43
|
+
if not man.is_file():
|
|
44
|
+
continue
|
|
45
|
+
mdata = man.read_bytes()
|
|
46
|
+
if b"\r" in mdata:
|
|
47
|
+
print(
|
|
48
|
+
f"guard_installer_publish: CR/LF (\\r) bytes found in {man.relative_to(ROOT)} — "
|
|
49
|
+
"use LF only (.gitattributes *.manifest; BUG-0008).",
|
|
50
|
+
file=sys.stderr,
|
|
51
|
+
)
|
|
52
|
+
return 1
|
|
53
|
+
text = data.decode("utf-8", errors="replace")
|
|
54
|
+
for token in FORBIDDEN_TOKENS:
|
|
55
|
+
if token in text:
|
|
56
|
+
print(
|
|
57
|
+
f"guard_installer_publish: forbidden startup token {token!r} in installer.sh",
|
|
58
|
+
file=sys.stderr,
|
|
59
|
+
)
|
|
60
|
+
return 1
|
|
61
|
+
dash = shutil.which("dash")
|
|
62
|
+
if dash:
|
|
63
|
+
r = subprocess.run(
|
|
64
|
+
[dash, "-n", str(INSTALLER_SH)],
|
|
65
|
+
cwd=ROOT,
|
|
66
|
+
capture_output=True,
|
|
67
|
+
text=True,
|
|
68
|
+
encoding="utf-8",
|
|
69
|
+
errors="replace",
|
|
70
|
+
)
|
|
71
|
+
if r.returncode != 0:
|
|
72
|
+
print(
|
|
73
|
+
"guard_installer_publish: dash -n installer.sh failed:\n"
|
|
74
|
+
+ (r.stderr or r.stdout or ""),
|
|
75
|
+
file=sys.stderr,
|
|
76
|
+
)
|
|
77
|
+
return 1
|
|
78
|
+
else:
|
|
79
|
+
print(
|
|
80
|
+
"guard_installer_publish: dash not on PATH; skipping dash -n "
|
|
81
|
+
"(Python CRLF + token checks still enforced).",
|
|
82
|
+
file=sys.stderr,
|
|
83
|
+
)
|
|
84
|
+
return 0
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
raise SystemExit(main())
|
|
@@ -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())
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Fail-closed guard: defect-shaped prose must not persist as US-xxxx without bug routing (DEC-0061 §5).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
# Strong defect signals (deterministic heuristic — PO still must set INTAKE_WORK_ITEM_KIND or /intake bug)
|
|
13
|
+
_REPRO = re.compile(
|
|
14
|
+
r"\b(steps\s+to\s+reproduce|steps_to_reproduce|repro\s+steps|reproduction\s+steps)\b",
|
|
15
|
+
re.IGNORECASE,
|
|
16
|
+
)
|
|
17
|
+
_DEFECT = re.compile(
|
|
18
|
+
r"\b(bug|regression|defect|crash|stack\s+trace|broken|throws\s+exception)\b",
|
|
19
|
+
re.IGNORECASE,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def prose_looks_like_defect(text: str) -> bool:
|
|
24
|
+
low = text.lower()
|
|
25
|
+
if not _DEFECT.search(low):
|
|
26
|
+
return False
|
|
27
|
+
if _REPRO.search(low):
|
|
28
|
+
return True
|
|
29
|
+
if "expected" in low and "actual" in low:
|
|
30
|
+
return True
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def main() -> int:
|
|
35
|
+
ap = argparse.ArgumentParser(
|
|
36
|
+
description="If work item kind is story but prose looks like a defect report, fail with INTAKE_BUG_ROUTING_REQUIRED."
|
|
37
|
+
)
|
|
38
|
+
ap.add_argument("--kind", choices=("story", "bug"), required=True)
|
|
39
|
+
ap.add_argument("--file", help="Path to prose file (title+summary)")
|
|
40
|
+
ap.add_argument("--stdin", action="store_true", help="Read prose from stdin")
|
|
41
|
+
args = ap.parse_args()
|
|
42
|
+
|
|
43
|
+
if args.kind == "bug":
|
|
44
|
+
print("[INTAKE_BUG_ROUTING_OK] kind=bug")
|
|
45
|
+
return 0
|
|
46
|
+
|
|
47
|
+
if args.stdin:
|
|
48
|
+
text = sys.stdin.read()
|
|
49
|
+
elif args.file:
|
|
50
|
+
text = open(args.file, encoding="utf-8").read()
|
|
51
|
+
else:
|
|
52
|
+
print("INTAKE_BUG_ROUTING_GUARD_ERROR: provide --file or --stdin", file=sys.stderr)
|
|
53
|
+
return 2
|
|
54
|
+
|
|
55
|
+
if prose_looks_like_defect(text):
|
|
56
|
+
print(
|
|
57
|
+
"INTAKE_BUG_ROUTING_REQUIRED: defect-shaped prose with INTAKE_WORK_ITEM_KIND=story "
|
|
58
|
+
"(set INTAKE_WORK_ITEM_KIND=bug and/or use `/intake bug` per DEC-0061 §5)",
|
|
59
|
+
file=sys.stderr,
|
|
60
|
+
)
|
|
61
|
+
return 3
|
|
62
|
+
print("[INTAKE_BUG_ROUTING_OK] kind=story")
|
|
63
|
+
return 0
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
raise SystemExit(main())
|