its-magic 0.1.2-36 → 0.1.2-38
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 +1 -0
- package/installer.ps1 +20 -0
- package/installer.py +66 -2
- package/installer.sh +19 -0
- package/package.json +6 -1
- package/scripts/check_intake_template_parity.py +52 -0
- package/scripts/intake_bug_routing_guard.py +67 -0
- package/scripts/intake_evidence_lib.py +703 -0
- package/scripts/intake_evidence_validate.py +73 -0
- 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/intake.md +35 -7
- 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/README.md +1 -0
- package/template/docs/engineering/context/installer-owned-paths.manifest +25 -0
- package/template/docs/engineering/runbook.md +76 -1
- package/template/scripts/check_intake_template_parity.py +52 -0
- package/template/scripts/enforce-triad-hot-surface.py +626 -0
- package/template/scripts/intake_bug_routing_guard.py +67 -0
- package/template/scripts/intake_evidence_lib.py +703 -0
- package/template/scripts/intake_evidence_validate.py +73 -0
- package/template/scripts/materialize_codebase_map.py +184 -0
package/README.md
CHANGED
|
@@ -367,6 +367,7 @@ deterministic **`intake_evidence`** gate — **`topic_coverage`** with valid **`
|
|
|
367
367
|
asked-vs-covered alignment, and **`assumption_confirmation_ref`** when assumptions are affirmative.
|
|
368
368
|
|
|
369
369
|
- Run `python scripts/intake_evidence_validate.py --self-test` (also exercised via `tests/run-tests.*` §26k).
|
|
370
|
+
- **Packaged installs (BUG-0001 / DEC-0063)**: the intake gate modules (`intake_evidence_validate.py`, `intake_evidence_lib.py`, `intake_bug_routing_guard.py`) ship under **`template/scripts/`** and hydrate consumer repos at **`scripts/`** (npm **`files`**, Chocolatey/Homebrew **`template/`** tree, **`installer.ps1` / `installer.sh`** + **`installer-owned-paths.manifest`**). **`--mode upgrade`** treats them as framework files (added/updated like other shipped scripts). CI parity: **`python scripts/check_intake_template_parity.py --repo .`** (`tests/run-tests.*` §26N).
|
|
370
371
|
- Operator docs: **`decisions/DEC-0060.md`**, **`docs/engineering/architecture.md`** **`# US-0078`**, runbook section **Interactive intake evidence validation (US-0078 / DEC-0060)**.
|
|
371
372
|
- **Guided** and **low-touch** share the **same pre-persistence validation pipeline**; low-touch does not bypass mandatory pack coverage.
|
|
372
373
|
|
package/installer.ps1
CHANGED
|
@@ -405,6 +405,24 @@ function Invoke-ScratchpadPostinstall {
|
|
|
405
405
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
406
406
|
}
|
|
407
407
|
|
|
408
|
+
function Invoke-InstallCompletenessValidation {
|
|
409
|
+
param(
|
|
410
|
+
[string]$TargetRoot
|
|
411
|
+
)
|
|
412
|
+
$installerPy = Join-Path $scriptDir "installer.py"
|
|
413
|
+
if (-not (Test-Path $installerPy -PathType Leaf)) {
|
|
414
|
+
Write-Host "[INSTALL_COMPLETENESS_FAILED] installer.py missing next to installer.ps1."
|
|
415
|
+
exit 1
|
|
416
|
+
}
|
|
417
|
+
$py = Get-Command python -ErrorAction SilentlyContinue
|
|
418
|
+
if (-not $py) {
|
|
419
|
+
Write-Host "[INSTALL_COMPLETENESS_FAILED] PYTHON_NOT_FOUND: Python is required for deterministic installer completeness validation."
|
|
420
|
+
exit 1
|
|
421
|
+
}
|
|
422
|
+
& python $installerPy --validate-install-completeness --target $TargetRoot
|
|
423
|
+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
424
|
+
}
|
|
425
|
+
|
|
408
426
|
function Show-ItsMagicBanner([switch]$IncludeInstallMessage) {
|
|
409
427
|
$prev = [Console]::OutputEncoding
|
|
410
428
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
@@ -643,6 +661,7 @@ if ($mode -eq "upgrade") {
|
|
|
643
661
|
}
|
|
644
662
|
|
|
645
663
|
Invoke-ScratchpadPostinstall -TargetRoot $targetRoot -Mode "upgrade"
|
|
664
|
+
Invoke-InstallCompletenessValidation -TargetRoot $targetRoot
|
|
646
665
|
|
|
647
666
|
Write-InstalledVersion $targetRoot $appVersion
|
|
648
667
|
Sync-RootReadmeToItsMagic $targetRoot | Out-Null
|
|
@@ -724,6 +743,7 @@ foreach ($rel in $files) {
|
|
|
724
743
|
}
|
|
725
744
|
|
|
726
745
|
Invoke-ScratchpadPostinstall -TargetRoot $targetRoot -Mode $mode
|
|
746
|
+
Invoke-InstallCompletenessValidation -TargetRoot $targetRoot
|
|
727
747
|
|
|
728
748
|
Write-InstalledVersion $targetRoot $appVersion
|
|
729
749
|
Sync-RootReadmeToItsMagic $targetRoot | Out-Null
|
package/installer.py
CHANGED
|
@@ -11,6 +11,7 @@ from datetime import datetime
|
|
|
11
11
|
|
|
12
12
|
REPO_URL = "https://github.com/fl0wm0ti0n/its-magic"
|
|
13
13
|
MANIFEST_RELATIVE_PATH = os.path.join("docs", "engineering", "context", "installer-owned-paths.manifest")
|
|
14
|
+
MANIFEST_REQUIRED_SCRIPTS_SECTION = "required_install_script_paths"
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
def normalize(path):
|
|
@@ -67,12 +68,42 @@ def load_ownership_manifest(source_root, script_dir):
|
|
|
67
68
|
continue
|
|
68
69
|
install_paths = read_manifest_paths(path, "install_include_paths")
|
|
69
70
|
clean_paths = read_manifest_paths(path, "clean_paths")
|
|
71
|
+
required_script_paths = read_manifest_paths(path, MANIFEST_REQUIRED_SCRIPTS_SECTION)
|
|
70
72
|
if not install_paths or not clean_paths:
|
|
71
73
|
raise RuntimeError(f"[INSTALL_MANIFEST_ERROR] {path} is missing required sections or entries.")
|
|
72
|
-
|
|
74
|
+
if not required_script_paths:
|
|
75
|
+
raise RuntimeError(
|
|
76
|
+
f"[INSTALL_MANIFEST_ERROR] {path} is missing [{MANIFEST_REQUIRED_SCRIPTS_SECTION}] entries."
|
|
77
|
+
)
|
|
78
|
+
return install_paths, clean_paths, required_script_paths, path
|
|
73
79
|
raise RuntimeError("[INSTALL_SOURCE_ERROR] installer-owned-paths.manifest not found. Reinstall its-magic package.")
|
|
74
80
|
|
|
75
81
|
|
|
82
|
+
def validate_install_completeness(target_root, source_root, required_script_paths, manifest_path):
|
|
83
|
+
missing_paths = []
|
|
84
|
+
for rel in sorted(set(required_script_paths)):
|
|
85
|
+
src = os.path.join(source_root, rel)
|
|
86
|
+
dst = os.path.join(target_root, rel)
|
|
87
|
+
if not os.path.isfile(src) or not os.path.isfile(dst):
|
|
88
|
+
missing_paths.append(rel.replace("\\", "/"))
|
|
89
|
+
if not missing_paths:
|
|
90
|
+
return True
|
|
91
|
+
print(
|
|
92
|
+
"[INSTALL_COMPLETENESS_FAILED] Required installer scripts are missing after "
|
|
93
|
+
"copy/classification invariant check."
|
|
94
|
+
)
|
|
95
|
+
for rel in missing_paths:
|
|
96
|
+
print(f"[INSTALL_REQUIRED_SCRIPT_MISSING:{rel}]")
|
|
97
|
+
print(
|
|
98
|
+
"Fix: update manifest parity and required-script inventory at "
|
|
99
|
+
f"{MANIFEST_RELATIVE_PATH} (section [{MANIFEST_REQUIRED_SCRIPTS_SECTION}]), "
|
|
100
|
+
"ensure each listed script exists in template/scripts and clean-path ownership, "
|
|
101
|
+
"then rerun installer missing/upgrade."
|
|
102
|
+
)
|
|
103
|
+
print(f"Manifest source: {manifest_path}")
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
|
|
76
107
|
def ensure_parent(path):
|
|
77
108
|
parent = os.path.dirname(path)
|
|
78
109
|
if parent and not os.path.isdir(parent):
|
|
@@ -684,6 +715,12 @@ def main():
|
|
|
684
715
|
action="store_true",
|
|
685
716
|
help=argparse.SUPPRESS,
|
|
686
717
|
)
|
|
718
|
+
parser.add_argument("--source-root", help=argparse.SUPPRESS)
|
|
719
|
+
parser.add_argument(
|
|
720
|
+
"--validate-install-completeness",
|
|
721
|
+
action="store_true",
|
|
722
|
+
help=argparse.SUPPRESS,
|
|
723
|
+
)
|
|
687
724
|
args = parser.parse_args()
|
|
688
725
|
|
|
689
726
|
if len(sys.argv) == 1 or args.help:
|
|
@@ -694,6 +731,9 @@ def main():
|
|
|
694
731
|
print(f"its-magic v{version}")
|
|
695
732
|
return 0
|
|
696
733
|
|
|
734
|
+
if args.source_root:
|
|
735
|
+
source_root = normalize(args.source_root)
|
|
736
|
+
|
|
697
737
|
if args.scratchpad_postinstall:
|
|
698
738
|
target_root = normalize(args.target) if args.target else normalize(".")
|
|
699
739
|
mode = args.mode or "missing"
|
|
@@ -712,11 +752,31 @@ def main():
|
|
|
712
752
|
ok = run_scratchpad_postinstall(target_root, source_root, mode, print_ok=True)
|
|
713
753
|
return 0 if ok else 1
|
|
714
754
|
|
|
755
|
+
if args.validate_install_completeness:
|
|
756
|
+
target_root = normalize(args.target) if args.target else normalize(".")
|
|
757
|
+
if not os.path.isdir(target_root):
|
|
758
|
+
print(f"[INSTALL_COMPLETENESS_FAILED] target directory missing: {target_root}")
|
|
759
|
+
return 1
|
|
760
|
+
if not os.path.isdir(source_root):
|
|
761
|
+
print("[INSTALL_SOURCE_ERROR] template directory is missing. Reinstall its-magic package.")
|
|
762
|
+
return 1
|
|
763
|
+
try:
|
|
764
|
+
_install_paths, _clean_paths, required_script_paths, manifest_path = load_ownership_manifest(
|
|
765
|
+
source_root, script_dir
|
|
766
|
+
)
|
|
767
|
+
except RuntimeError as exc:
|
|
768
|
+
print(str(exc))
|
|
769
|
+
return 1
|
|
770
|
+
ok = validate_install_completeness(target_root, source_root, required_script_paths, manifest_path)
|
|
771
|
+
return 0 if ok else 1
|
|
772
|
+
|
|
715
773
|
if not os.path.isdir(source_root):
|
|
716
774
|
print("[INSTALL_SOURCE_ERROR] template directory is missing. Reinstall its-magic package.")
|
|
717
775
|
return 1
|
|
718
776
|
try:
|
|
719
|
-
include_paths, clean_paths = load_ownership_manifest(
|
|
777
|
+
include_paths, clean_paths, required_script_paths, manifest_path = load_ownership_manifest(
|
|
778
|
+
source_root, script_dir
|
|
779
|
+
)
|
|
720
780
|
except RuntimeError as exc:
|
|
721
781
|
print(str(exc))
|
|
722
782
|
return 1
|
|
@@ -818,6 +878,8 @@ def main():
|
|
|
818
878
|
|
|
819
879
|
if not run_scratchpad_postinstall(target_root, source_root, "upgrade", print_ok=True):
|
|
820
880
|
return 1
|
|
881
|
+
if not validate_install_completeness(target_root, source_root, required_script_paths, manifest_path):
|
|
882
|
+
return 1
|
|
821
883
|
|
|
822
884
|
write_installed_version(target_root, version)
|
|
823
885
|
sync_root_readme_to_its_magic(target_root)
|
|
@@ -896,6 +958,8 @@ def main():
|
|
|
896
958
|
|
|
897
959
|
if not run_scratchpad_postinstall(target_root, source_root, mode, print_ok=True):
|
|
898
960
|
return 1
|
|
961
|
+
if not validate_install_completeness(target_root, source_root, required_script_paths, manifest_path):
|
|
962
|
+
return 1
|
|
899
963
|
|
|
900
964
|
write_installed_version(target_root, version)
|
|
901
965
|
sync_root_readme_to_its_magic(target_root)
|
package/installer.sh
CHANGED
|
@@ -151,6 +151,23 @@ scratchpad_postinstall() {
|
|
|
151
151
|
fi
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
validate_install_completeness() {
|
|
155
|
+
target_root="$1"
|
|
156
|
+
installer_py="$SCRIPT_DIR/installer.py"
|
|
157
|
+
if [ ! -f "$installer_py" ]; then
|
|
158
|
+
printf "%s\n" "[INSTALL_COMPLETENESS_FAILED] installer.py missing next to installer.sh."
|
|
159
|
+
exit 1
|
|
160
|
+
fi
|
|
161
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
162
|
+
python3 "$installer_py" --validate-install-completeness --target "$target_root" || exit $?
|
|
163
|
+
elif command -v python >/dev/null 2>&1; then
|
|
164
|
+
python "$installer_py" --validate-install-completeness --target "$target_root" || exit $?
|
|
165
|
+
else
|
|
166
|
+
printf "%s\n" "[INSTALL_COMPLETENESS_FAILED] PYTHON_NOT_FOUND: Python is required for deterministic installer completeness validation."
|
|
167
|
+
exit 1
|
|
168
|
+
fi
|
|
169
|
+
}
|
|
170
|
+
|
|
154
171
|
classify_file() {
|
|
155
172
|
rel="$1"
|
|
156
173
|
case "$rel" in
|
|
@@ -535,6 +552,7 @@ if [ "$MODE" = "upgrade" ]; then
|
|
|
535
552
|
done
|
|
536
553
|
|
|
537
554
|
scratchpad_postinstall "$TARGET_ROOT" "upgrade"
|
|
555
|
+
validate_install_completeness "$TARGET_ROOT"
|
|
538
556
|
|
|
539
557
|
write_installed_version "$TARGET_ROOT" "$APP_VERSION"
|
|
540
558
|
sync_root_readme_to_its_magic "$TARGET_ROOT" || true
|
|
@@ -606,6 +624,7 @@ for rel in $FILES; do
|
|
|
606
624
|
done
|
|
607
625
|
|
|
608
626
|
scratchpad_postinstall "$TARGET_ROOT" "$MODE"
|
|
627
|
+
validate_install_completeness "$TARGET_ROOT"
|
|
609
628
|
|
|
610
629
|
write_installed_version "$TARGET_ROOT" "$APP_VERSION"
|
|
611
630
|
sync_root_readme_to_its_magic "$TARGET_ROOT" || true
|
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-38",
|
|
4
4
|
"description": "its-magic - AI dev team workflow for Cursor.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
"installer.sh",
|
|
13
13
|
"installer.py",
|
|
14
14
|
"scripts/doc_profile_lib.py",
|
|
15
|
+
"scripts/intake_evidence_validate.py",
|
|
16
|
+
"scripts/intake_evidence_lib.py",
|
|
17
|
+
"scripts/intake_bug_routing_guard.py",
|
|
18
|
+
"scripts/check_intake_template_parity.py",
|
|
19
|
+
"scripts/materialize_codebase_map.py",
|
|
15
20
|
"bin/its-magic.js",
|
|
16
21
|
"bin/postinstall.js"
|
|
17
22
|
],
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Verify active vs template/scripts/ bytes match for DEC-0063 intake gate modules (BUG-0001)."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
# Normative pairs: repo scripts/ (canonical dev) → template/scripts/ (packaged ship path).
|
|
11
|
+
INTAKE_TEMPLATE_PAIRS: tuple[tuple[str, str], ...] = (
|
|
12
|
+
("scripts/intake_evidence_validate.py", "template/scripts/intake_evidence_validate.py"),
|
|
13
|
+
("scripts/intake_evidence_lib.py", "template/scripts/intake_evidence_lib.py"),
|
|
14
|
+
("scripts/intake_bug_routing_guard.py", "template/scripts/intake_bug_routing_guard.py"),
|
|
15
|
+
("scripts/check_intake_template_parity.py", "template/scripts/check_intake_template_parity.py"),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main() -> int:
|
|
20
|
+
p = argparse.ArgumentParser(description=__doc__)
|
|
21
|
+
p.add_argument(
|
|
22
|
+
"--repo",
|
|
23
|
+
type=Path,
|
|
24
|
+
default=Path(__file__).resolve().parent.parent,
|
|
25
|
+
help="Repository root",
|
|
26
|
+
)
|
|
27
|
+
args = p.parse_args()
|
|
28
|
+
root: Path = args.repo
|
|
29
|
+
failed = False
|
|
30
|
+
for rel_active, rel_tpl in INTAKE_TEMPLATE_PAIRS:
|
|
31
|
+
a = root / rel_active
|
|
32
|
+
t = root / rel_tpl
|
|
33
|
+
if not a.is_file() or not t.is_file():
|
|
34
|
+
print(f"[INTAKE_TEMPLATE_PARITY_ERROR] missing file: {rel_active} or {rel_tpl}")
|
|
35
|
+
failed = True
|
|
36
|
+
continue
|
|
37
|
+
ba = a.read_bytes()
|
|
38
|
+
bt = t.read_bytes()
|
|
39
|
+
if ba != bt:
|
|
40
|
+
print(
|
|
41
|
+
f"[INTAKE_TEMPLATE_PARITY_ERROR] mismatch: {rel_active} ({len(ba)}b) "
|
|
42
|
+
f"!= {rel_tpl} ({len(bt)}b)"
|
|
43
|
+
)
|
|
44
|
+
failed = True
|
|
45
|
+
if failed:
|
|
46
|
+
return 2
|
|
47
|
+
print("[INTAKE_TEMPLATE_PARITY_OK]")
|
|
48
|
+
return 0
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
sys.exit(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())
|