its-magic 0.1.2-29 → 0.1.2-31
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 +62 -13
- package/bin/its-magic.js +4 -0
- package/installer.ps1 +27 -7
- package/installer.py +170 -5
- package/installer.sh +27 -2
- package/package.json +1 -1
- package/template/.cursor/commands/architecture.md +8 -0
- package/template/.cursor/commands/auto.md +266 -11
- package/template/.cursor/commands/discovery.md +11 -0
- package/template/.cursor/commands/execute.md +23 -0
- package/template/.cursor/commands/intake.md +8 -0
- package/template/.cursor/commands/qa.md +6 -0
- package/template/.cursor/commands/refresh-context.md +18 -8
- package/template/.cursor/commands/release.md +9 -1
- package/template/.cursor/rules/quality.mdc +7 -0
- package/template/.cursor/scratchpad.local.example.md +30 -3
- package/template/.cursor/scratchpad.md +46 -17
- package/template/README.md +64 -17
- package/template/docs/engineering/context/installer-owned-paths.manifest +0 -1
- package/template/docs/engineering/phase-context.md +16 -0
- package/template/docs/engineering/runbook.md +225 -10
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@ with pause/resume, decision gates, and persistent artifacts.
|
|
|
16
16
|
- Pause/resume with checkpoints (`handoffs/resume_brief.md`).
|
|
17
17
|
- Automated execute/QA loop with safety caps (optional).
|
|
18
18
|
- 3-layer quality chain: AI loop → local validate-and-push → CI auto-fix.
|
|
19
|
+
- User-visible metadata guard for operator-facing scripts/CLI (`US-0071` / `DEC-0053`):
|
|
20
|
+
`python scripts/check-user-visible-metadata.py` (see `docs/engineering/runbook.md`).
|
|
19
21
|
- CI/CD templates driven by `docs/engineering/runbook.md`.
|
|
20
22
|
- Team-friendly local overrides (`scratchpad.local.md`).
|
|
21
23
|
- Optional remote/docker execution and autonomous installs.
|
|
@@ -78,8 +80,13 @@ What upgrade does:
|
|
|
78
80
|
- **Framework files** (commands, rules, agents, hooks, skills, CI, scripts) are
|
|
79
81
|
updated to the latest version.
|
|
80
82
|
- **User data** (docs, sprints, handoffs, decisions, runbook) is never touched.
|
|
81
|
-
- **Mixed files** (
|
|
82
|
-
|
|
83
|
+
- **Mixed files** (`README.md`) are preserved. If the template version has new
|
|
84
|
+
content, a review notice is printed.
|
|
85
|
+
- **Scratchpad baseline (DEC-0055 / US-0073, Model B):** `.cursor/scratchpad.md`
|
|
86
|
+
is not copied as a manifest file; the installer **materializes** it from the
|
|
87
|
+
packaged template when missing and validates required merged keys (Python
|
|
88
|
+
required). Legacy repos that already committed `.cursor/scratchpad.md` keep it on
|
|
89
|
+
upgrade (not overwritten).
|
|
83
90
|
- A canonical version marker is stored at `its_magic/.its-magic-version` in your repo.
|
|
84
91
|
- Installer bootstrap is OS-aware + stack-aware for runbook command defaults
|
|
85
92
|
(`TEST_COMMAND`, optional `LINT_COMMAND`/`TYPECHECK_COMMAND`) and preserves
|
|
@@ -177,8 +184,8 @@ your-project/
|
|
|
177
184
|
.cursor/agents/ Subagent definitions
|
|
178
185
|
.cursor/skills/ Reusable skills
|
|
179
186
|
.cursor/hooks/ Automation hooks
|
|
180
|
-
.cursor/scratchpad.md
|
|
181
|
-
.cursor/scratchpad.local.example.md
|
|
187
|
+
.cursor/scratchpad.md Materialized shared defaults (Model B; not manifest-copied)
|
|
188
|
+
.cursor/scratchpad.local.example.md Framework default key catalog
|
|
182
189
|
docs/ Engineering & product docs, runbook
|
|
183
190
|
sprints/ Sprint tracking artifacts
|
|
184
191
|
handoffs/ Phase handoff artifacts
|
|
@@ -191,20 +198,30 @@ your-project/
|
|
|
191
198
|
|
|
192
199
|
### Team mode local overrides (recommended)
|
|
193
200
|
|
|
194
|
-
Use
|
|
201
|
+
Use three layers (merge precedence: **local > materialized baseline > example**,
|
|
202
|
+
`DEC-0055`):
|
|
195
203
|
|
|
196
|
-
-
|
|
197
|
-
-
|
|
204
|
+
- Framework catalog: `.cursor/scratchpad.local.example.md` (installed; refreshed on upgrade)
|
|
205
|
+
- Shared team baseline: `.cursor/scratchpad.md` (materialized on install when missing; commit as you prefer)
|
|
206
|
+
- Personal overrides: `.cursor/scratchpad.local.md` (gitignored; never overwritten by install/upgrade)
|
|
198
207
|
|
|
199
208
|
Setup:
|
|
200
209
|
|
|
201
|
-
1.
|
|
202
|
-
2.
|
|
203
|
-
|
|
210
|
+
1. Run `its-magic` — baseline is materialized and merged validation runs (requires Python on PATH for `installer.ps1` / `installer.sh`).
|
|
211
|
+
2. Optionally copy `.cursor/scratchpad.local.example.md` to `.cursor/scratchpad.local.md` for personal values (`TEAM_MEMBER`, `ACTIVE_TASK_IDS`, …).
|
|
212
|
+
|
|
213
|
+
Recovery if `.cursor/scratchpad.md` is missing or merge validation fails:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
python installer.py --scratchpad-postinstall --target . --mode missing
|
|
217
|
+
```
|
|
204
218
|
|
|
205
219
|
Upgrade behavior (US-0057):
|
|
220
|
+
- Aligns with **DEC-0039** (example vs local ownership) and Model B baseline rules below.
|
|
221
|
+
|
|
206
222
|
- `.cursor/scratchpad.local.example.md` is framework-owned and refreshed on `--mode upgrade`.
|
|
207
223
|
- `.cursor/scratchpad.local.md` is user-owned and preserved on `--mode upgrade`.
|
|
224
|
+
- Existing `.cursor/scratchpad.md` is left untouched on upgrade (legacy parity).
|
|
208
225
|
- Installer output includes scratchpad example refresh status and local-preserved signal.
|
|
209
226
|
|
|
210
227
|
Deterministic ordering behavior (US-0058):
|
|
@@ -383,9 +400,15 @@ Compaction behavior:
|
|
|
383
400
|
- Enforced rollover thresholds:
|
|
384
401
|
- `STATE_HOT_MAX_LINES` (default `1200`)
|
|
385
402
|
- `STATE_HOT_MAX_CHECKPOINTS` (default `80`)
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
403
|
+
- `PO_TO_TL_HOT_MAX_LINES` (default `800`)
|
|
404
|
+
- `PO_TO_TL_HOT_MAX_SECTIONS` (default `60`)
|
|
405
|
+
- `ARCH_HOT_MAX_LINES` (default `3500`)
|
|
406
|
+
- `ARCH_HOT_MAX_STORY_SECTIONS` (default `120`)
|
|
407
|
+
Triad hot surfaces (`state.md`, `handoffs/po_to_tl.md`,
|
|
408
|
+
`docs/engineering/architecture.md`) must stay within merged scratchpad caps.
|
|
409
|
+
Use `python scripts/enforce-triad-hot-surface.py --check` before completing a
|
|
410
|
+
phase that mutates them; use `--rollover` to archive oldest material into
|
|
411
|
+
deterministic packs when over cap (DEC-0054).
|
|
389
412
|
Archive verification mismatch fails with
|
|
390
413
|
`STATE_ARCHIVE_VERIFICATION_FAILED`.
|
|
391
414
|
|
|
@@ -619,6 +642,32 @@ Fail-closed reason codes:
|
|
|
619
642
|
`/auto`, `/verify-work`, and `/release` must validate these tuples before
|
|
620
643
|
continuation/finalization.
|
|
621
644
|
|
|
645
|
+
#### `/auto` phase→role enforcement (US-0069 / DEC-0051)
|
|
646
|
+
|
|
647
|
+
`/auto` uses a deterministic **phase→role matrix** plus scratchpad alternates
|
|
648
|
+
(`AUTO_ROLE_RESEARCH`, `AUTO_ROLE_PLAN_VERIFY`, `AUTO_ROLE_REFRESH_CONTEXT`).
|
|
649
|
+
Before each phase spawn it runs a **preflight capability gate**; missing
|
|
650
|
+
capability stops with `PHASE_ROLE_CAPABILITY_MISSING` (no unrelated-role
|
|
651
|
+
substitution). After each phase, isolation `role` and strict-proof `role` must
|
|
652
|
+
match the same expected role or the run stops with `PHASE_ROLE_MISMATCH`.
|
|
653
|
+
`execute` defaults to `dev`; non-`dev` requires
|
|
654
|
+
`AUTO_EXECUTE_ROLE_OVERRIDE=allowed_non_dev_execute` **and**
|
|
655
|
+
`EXECUTE_OVERRIDE_GOVERNANCE_REF` pointing to a parseable approved waiver. See
|
|
656
|
+
`docs/engineering/runbook.md` and `decisions/DEC-0051.md`.
|
|
657
|
+
|
|
658
|
+
#### `/auto` phase selection policy (US-0070 / DEC-0052)
|
|
659
|
+
|
|
660
|
+
`/auto` builds a **resolved phase plan** from scratchpad before spawning phases:
|
|
661
|
+
exactly one of `AUTO_PHASE_PLAN` (default `full`), `AUTO_PHASE_EXCLUDE`,
|
|
662
|
+
`AUTO_PHASE_INCLUDE`, or `AUTO_PHASE_PROFILE` applies; conflicting selectors
|
|
663
|
+
stop with `PHASE_POLICY_CONFLICT`. Non-skippable safety gates (`qa`,
|
|
664
|
+
`verify-work`, `release`) and evidence-chain closure reinstate omitted phases
|
|
665
|
+
with breadcrumb reasons such as `non_skippable_gate`. `start-from` and resume
|
|
666
|
+
anchors **intersect** with the plan (`START_FROM_PHASE_PLAN_EMPTY_INTERSECTION`
|
|
667
|
+
when empty). Backlog-drain, bulk execute, and team-mode runs **recompute** the
|
|
668
|
+
plan each boundary. See `/auto`, `docs/engineering/runbook.md`, and
|
|
669
|
+
`decisions/DEC-0052.md`.
|
|
670
|
+
|
|
622
671
|
### Lightweight interaction
|
|
623
672
|
|
|
624
673
|
Use `/ask` when you want to query the project without triggering the workflow:
|
package/bin/its-magic.js
CHANGED
|
@@ -118,6 +118,10 @@ Install options:
|
|
|
118
118
|
Note: installer bootstraps runbook TEST/LINT/TYPECHECK commands from
|
|
119
119
|
OS+stack detection; unresolved TEST_COMMAND fails fast with
|
|
120
120
|
[RUNBOOK_BOOTSTRAP_ERROR] diagnostics.
|
|
121
|
+
Note: scratchpad Model B: .cursor/scratchpad.md is
|
|
122
|
+
materialized when missing; PowerShell/bash installers require Python 3
|
|
123
|
+
on PATH for merged scratchpad validation. Recovery:
|
|
124
|
+
python installer.py --scratchpad-postinstall --target <repo> --mode missing
|
|
121
125
|
|
|
122
126
|
Clean options:
|
|
123
127
|
--clean-repo Remove all its-magic workflow artifacts from the target repo
|
package/installer.ps1
CHANGED
|
@@ -111,7 +111,7 @@ function Choose-Mode {
|
|
|
111
111
|
function Classify-File($RelPath) {
|
|
112
112
|
$normalized = $RelPath -replace '\\','/'
|
|
113
113
|
|
|
114
|
-
$mixedFiles = @('
|
|
114
|
+
$mixedFiles = @('README.md')
|
|
115
115
|
if ($mixedFiles -contains $normalized) { return 'mixed' }
|
|
116
116
|
|
|
117
117
|
$frameworkPrefixes = @(
|
|
@@ -232,7 +232,6 @@ function Get-DetectedRunbookDefaults($TargetRoot) {
|
|
|
232
232
|
TYPECHECK_COMMAND = ""
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
$testsPs1 = Join-Path $TargetRoot "tests\run-tests.ps1"
|
|
236
235
|
$testsSh = Join-Path $TargetRoot "tests\run-tests.sh"
|
|
237
236
|
$pkgPath = Join-Path $TargetRoot "package.json"
|
|
238
237
|
$goMod = Join-Path $TargetRoot "go.mod"
|
|
@@ -261,11 +260,6 @@ function Get-DetectedRunbookDefaults($TargetRoot) {
|
|
|
261
260
|
return $defaults
|
|
262
261
|
}
|
|
263
262
|
|
|
264
|
-
if (Test-Path $testsPs1 -PathType Leaf) {
|
|
265
|
-
$defaults.TEST_COMMAND = "powershell -ExecutionPolicy Bypass -File `"tests/run-tests.ps1`""
|
|
266
|
-
return $defaults
|
|
267
|
-
}
|
|
268
|
-
|
|
269
263
|
if (Test-Path $testsSh -PathType Leaf) {
|
|
270
264
|
$defaults.TEST_COMMAND = "sh tests/run-tests.sh"
|
|
271
265
|
return $defaults
|
|
@@ -391,6 +385,25 @@ function Get-AppVersion($SourceRoot) {
|
|
|
391
385
|
return "unknown"
|
|
392
386
|
}
|
|
393
387
|
|
|
388
|
+
function Invoke-ScratchpadPostinstall {
|
|
389
|
+
param(
|
|
390
|
+
[string]$TargetRoot,
|
|
391
|
+
[string]$Mode
|
|
392
|
+
)
|
|
393
|
+
$installerPy = Join-Path $scriptDir "installer.py"
|
|
394
|
+
if (-not (Test-Path $installerPy -PathType Leaf)) {
|
|
395
|
+
Write-Host "[SCRATCHPAD_POSTINSTALL_ERROR] installer.py missing next to installer.ps1."
|
|
396
|
+
exit 1
|
|
397
|
+
}
|
|
398
|
+
$py = Get-Command python -ErrorAction SilentlyContinue
|
|
399
|
+
if (-not $py) {
|
|
400
|
+
Write-Host "[SCRATCHPAD_POSTINSTALL_ERROR] PYTHON_NOT_FOUND: Python is required for scratchpad materialization/validation (Model B). Fix: install Python 3 and re-run."
|
|
401
|
+
exit 1
|
|
402
|
+
}
|
|
403
|
+
& python $installerPy --scratchpad-postinstall --target $TargetRoot --mode $Mode
|
|
404
|
+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
405
|
+
}
|
|
406
|
+
|
|
394
407
|
function Show-ItsMagicBanner([switch]$IncludeInstallMessage) {
|
|
395
408
|
$prev = [Console]::OutputEncoding
|
|
396
409
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
@@ -440,6 +453,9 @@ function Show-ItsMagicHelp($VersionString, $RepoUrl) {
|
|
|
440
453
|
Write-Host " Note: installer bootstraps runbook TEST/LINT/TYPECHECK commands from"
|
|
441
454
|
Write-Host " OS+stack detection; unresolved TEST_COMMAND fails fast with"
|
|
442
455
|
Write-Host " [RUNBOOK_BOOTSTRAP_ERROR] diagnostics."
|
|
456
|
+
Write-Host " Note: scratchpad Model B: .cursor/scratchpad.md is"
|
|
457
|
+
Write-Host " materialized when missing; Python 3 on PATH is required for validation."
|
|
458
|
+
Write-Host " Recovery: python installer.py --scratchpad-postinstall --target <repo> --mode missing"
|
|
443
459
|
Write-Host ""
|
|
444
460
|
Write-Host "Clean options:"
|
|
445
461
|
Write-Host " --clean-repo Remove all its-magic workflow artifacts from the target repo"
|
|
@@ -625,6 +641,8 @@ if ($mode -eq "upgrade") {
|
|
|
625
641
|
}
|
|
626
642
|
}
|
|
627
643
|
|
|
644
|
+
Invoke-ScratchpadPostinstall -TargetRoot $targetRoot -Mode "upgrade"
|
|
645
|
+
|
|
628
646
|
Write-InstalledVersion $targetRoot $appVersion
|
|
629
647
|
Sync-RootReadmeToItsMagic $targetRoot | Out-Null
|
|
630
648
|
$runbookBootstrap = Invoke-RunbookBootstrap -TargetRoot $targetRoot
|
|
@@ -703,6 +721,8 @@ foreach ($rel in $files) {
|
|
|
703
721
|
}
|
|
704
722
|
}
|
|
705
723
|
|
|
724
|
+
Invoke-ScratchpadPostinstall -TargetRoot $targetRoot -Mode $mode
|
|
725
|
+
|
|
706
726
|
Write-InstalledVersion $targetRoot $appVersion
|
|
707
727
|
Sync-RootReadmeToItsMagic $targetRoot | Out-Null
|
|
708
728
|
$runbookBootstrap = Invoke-RunbookBootstrap -TargetRoot $targetRoot
|
package/installer.py
CHANGED
|
@@ -119,7 +119,143 @@ USER_DATA_PREFIXES = (
|
|
|
119
119
|
"docs/product/", "docs/engineering/", "docs/user-guides/",
|
|
120
120
|
"sprints/", "handoffs/", "decisions/",
|
|
121
121
|
)
|
|
122
|
-
MIXED_FILES = {"
|
|
122
|
+
MIXED_FILES = {"README.md"}
|
|
123
|
+
|
|
124
|
+
# Model B (DEC-0055 / US-0073): baseline bytes live in template only; installs materialize `.cursor/scratchpad.md`.
|
|
125
|
+
SCRATCHPAD_BASELINE_REL = os.path.join(".cursor", "scratchpad.md")
|
|
126
|
+
SCRATCHPAD_EXAMPLE_REL = os.path.join(".cursor", "scratchpad.local.example.md")
|
|
127
|
+
SCRATCHPAD_LOCAL_REL = os.path.join(".cursor", "scratchpad.local.md")
|
|
128
|
+
|
|
129
|
+
# After merge (local > baseline > example), these must be non-empty (fail closed).
|
|
130
|
+
REQUIRED_SCRATCHPAD_KEYS = (
|
|
131
|
+
"MAGIC_CONTEXT_STRICT",
|
|
132
|
+
"AUTO_FLOW_MODE",
|
|
133
|
+
"PHASE_MODE",
|
|
134
|
+
"PERMISSION_MODE",
|
|
135
|
+
"AUTO_LOOP_MAX_CYCLES",
|
|
136
|
+
"SYNC_POLICY_MODE",
|
|
137
|
+
"DONE",
|
|
138
|
+
"TEAM_MODE",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def parse_scratchpad_file(path):
|
|
143
|
+
"""Parse KEY=value lines; empty values are retained (explicit override to empty)."""
|
|
144
|
+
if not os.path.isfile(path):
|
|
145
|
+
return {}
|
|
146
|
+
out = {}
|
|
147
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
148
|
+
for raw in f:
|
|
149
|
+
line = raw.strip()
|
|
150
|
+
if not line or line.startswith("#") or line.startswith("- "):
|
|
151
|
+
continue
|
|
152
|
+
if "=" not in line:
|
|
153
|
+
continue
|
|
154
|
+
key, _, val = line.partition("=")
|
|
155
|
+
key = key.strip()
|
|
156
|
+
if not key:
|
|
157
|
+
continue
|
|
158
|
+
out[key] = val.strip()
|
|
159
|
+
return out
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def merge_scratchpad_layers(target_root):
|
|
163
|
+
"""
|
|
164
|
+
Model B merge precedence: local > materialized baseline > example (later wins only when key absent).
|
|
165
|
+
"""
|
|
166
|
+
ex_path = os.path.join(target_root, SCRATCHPAD_EXAMPLE_REL)
|
|
167
|
+
base_path = os.path.join(target_root, SCRATCHPAD_BASELINE_REL)
|
|
168
|
+
loc_path = os.path.join(target_root, SCRATCHPAD_LOCAL_REL)
|
|
169
|
+
example = parse_scratchpad_file(ex_path)
|
|
170
|
+
baseline = parse_scratchpad_file(base_path)
|
|
171
|
+
local = parse_scratchpad_file(loc_path)
|
|
172
|
+
merged = {}
|
|
173
|
+
all_keys = set(example) | set(baseline) | set(local)
|
|
174
|
+
for key in all_keys:
|
|
175
|
+
if key in local:
|
|
176
|
+
merged[key] = local[key]
|
|
177
|
+
elif key in baseline:
|
|
178
|
+
merged[key] = baseline[key]
|
|
179
|
+
elif key in example:
|
|
180
|
+
merged[key] = example[key]
|
|
181
|
+
paths = {"example": ex_path, "baseline": base_path, "local": loc_path}
|
|
182
|
+
return merged, paths
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def validate_merged_scratchpad(target_root):
|
|
186
|
+
"""Return (ok, list of diagnostic lines)."""
|
|
187
|
+
merged, paths = merge_scratchpad_layers(target_root)
|
|
188
|
+
diagnostics = []
|
|
189
|
+
if not os.path.isfile(paths["example"]):
|
|
190
|
+
diagnostics.append(
|
|
191
|
+
"[SCRATCHPAD_MERGE_ERROR] EXAMPLE_LAYER_MISSING: "
|
|
192
|
+
f".cursor/scratchpad.local.example.md not found under {target_root}. "
|
|
193
|
+
"Fix: re-run its-magic install/upgrade."
|
|
194
|
+
)
|
|
195
|
+
if not os.path.isfile(paths["baseline"]):
|
|
196
|
+
diagnostics.append(
|
|
197
|
+
"[SCRATCHPAD_MERGE_ERROR] MATERIALIZED_BASELINE_MISSING: "
|
|
198
|
+
f".cursor/scratchpad.md not found under {target_root}. "
|
|
199
|
+
"Fix: run `python installer.py --scratchpad-postinstall --target <repo> --mode missing` "
|
|
200
|
+
"or re-run its-magic install (Model B materialization; see docs)."
|
|
201
|
+
)
|
|
202
|
+
missing = []
|
|
203
|
+
for key in REQUIRED_SCRATCHPAD_KEYS:
|
|
204
|
+
val = merged.get(key)
|
|
205
|
+
if val is None or str(val).strip() == "":
|
|
206
|
+
missing.append(key)
|
|
207
|
+
if missing:
|
|
208
|
+
diagnostics.append(
|
|
209
|
+
"[SCRATCHPAD_MERGE_ERROR] REQUIRED_KEY_MISSING_AFTER_MERGE: "
|
|
210
|
+
f"keys={','.join(missing)}. Layers consulted: local, baseline|materialized, example "
|
|
211
|
+
f"({paths['local']}, {paths['baseline']}, {paths['example']}). "
|
|
212
|
+
"Fix: set non-empty values in .cursor/scratchpad.local.md or restore materialized baseline from template."
|
|
213
|
+
)
|
|
214
|
+
ok = not diagnostics
|
|
215
|
+
return ok, diagnostics
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def materialize_scratchpad_baseline(target_root, source_root, mode):
|
|
219
|
+
"""
|
|
220
|
+
Write stable baseline bytes from template when Model B requires it.
|
|
221
|
+
Never touches .cursor/scratchpad.local.md.
|
|
222
|
+
"""
|
|
223
|
+
src = os.path.join(source_root, SCRATCHPAD_BASELINE_REL)
|
|
224
|
+
dst = os.path.join(target_root, SCRATCHPAD_BASELINE_REL)
|
|
225
|
+
if not os.path.isfile(src):
|
|
226
|
+
print(
|
|
227
|
+
"[SCRATCHPAD_MATERIALIZE_ERROR] TEMPLATE_BASELINE_MISSING: "
|
|
228
|
+
f"expected template file at {src}. Reinstall its-magic package."
|
|
229
|
+
)
|
|
230
|
+
return False
|
|
231
|
+
if mode == "overwrite":
|
|
232
|
+
ensure_parent(dst)
|
|
233
|
+
shutil.copy2(src, dst)
|
|
234
|
+
return True
|
|
235
|
+
if mode == "upgrade":
|
|
236
|
+
if not os.path.isfile(dst):
|
|
237
|
+
ensure_parent(dst)
|
|
238
|
+
shutil.copy2(src, dst)
|
|
239
|
+
return True
|
|
240
|
+
# missing, interactive
|
|
241
|
+
if not os.path.isfile(dst):
|
|
242
|
+
ensure_parent(dst)
|
|
243
|
+
shutil.copy2(src, dst)
|
|
244
|
+
return True
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def run_scratchpad_postinstall(target_root, source_root, mode, print_ok=True):
|
|
248
|
+
if not materialize_scratchpad_baseline(target_root, source_root, mode):
|
|
249
|
+
return False
|
|
250
|
+
ok, diagnostics = validate_merged_scratchpad(target_root)
|
|
251
|
+
for line in diagnostics:
|
|
252
|
+
print(line)
|
|
253
|
+
if ok and print_ok:
|
|
254
|
+
print(
|
|
255
|
+
"[SCRATCHPAD_POSTINSTALL_OK] Model B: materialized baseline (if required) "
|
|
256
|
+
"and merged scratchpad validation passed."
|
|
257
|
+
)
|
|
258
|
+
return ok
|
|
123
259
|
|
|
124
260
|
|
|
125
261
|
def classify_file(rel_path):
|
|
@@ -213,8 +349,6 @@ def package_has_script(target_root, script_name):
|
|
|
213
349
|
|
|
214
350
|
|
|
215
351
|
def detect_runbook_defaults(target_root):
|
|
216
|
-
is_windows = os.name == "nt"
|
|
217
|
-
tests_ps1 = os.path.join(target_root, "tests", "run-tests.ps1")
|
|
218
352
|
tests_sh = os.path.join(target_root, "tests", "run-tests.sh")
|
|
219
353
|
has_pkg = os.path.isfile(os.path.join(target_root, "package.json"))
|
|
220
354
|
has_py = any(
|
|
@@ -235,8 +369,6 @@ def detect_runbook_defaults(target_root):
|
|
|
235
369
|
result["TEST_COMMAND"] = "go test ./..."
|
|
236
370
|
elif has_py:
|
|
237
371
|
result["TEST_COMMAND"] = "python -m pytest"
|
|
238
|
-
elif is_windows and os.path.isfile(tests_ps1):
|
|
239
|
-
result["TEST_COMMAND"] = "powershell -ExecutionPolicy Bypass -File \"tests/run-tests.ps1\""
|
|
240
372
|
elif os.path.isfile(tests_sh):
|
|
241
373
|
result["TEST_COMMAND"] = "sh tests/run-tests.sh"
|
|
242
374
|
|
|
@@ -392,6 +524,10 @@ def show_help(version):
|
|
|
392
524
|
print(" Note: installer bootstraps runbook TEST/LINT/TYPECHECK commands")
|
|
393
525
|
print(" from OS+stack detection; unresolved TEST_COMMAND fails fast with")
|
|
394
526
|
print(" [RUNBOOK_BOOTSTRAP_ERROR] diagnostics.")
|
|
527
|
+
print(" Note: scratchpad Model B: `.cursor/scratchpad.md` is")
|
|
528
|
+
print(" materialized from the packaged template when missing; merged validation")
|
|
529
|
+
print(" requires Python 3 on PATH for installer.ps1 / installer.sh. Recovery:")
|
|
530
|
+
print(" python installer.py --scratchpad-postinstall --target <repo> --mode missing")
|
|
395
531
|
print()
|
|
396
532
|
print("Clean options:")
|
|
397
533
|
print(" --clean-repo Remove all its-magic workflow artifacts from the target repo")
|
|
@@ -445,6 +581,11 @@ def main():
|
|
|
445
581
|
parser.add_argument("--yes", action="store_true", help="Skip clean confirmation prompt")
|
|
446
582
|
parser.add_argument("--help", "-h", action="store_true", help="Show help")
|
|
447
583
|
parser.add_argument("--version", "-v", action="store_true", help="Show version")
|
|
584
|
+
parser.add_argument(
|
|
585
|
+
"--scratchpad-postinstall",
|
|
586
|
+
action="store_true",
|
|
587
|
+
help=argparse.SUPPRESS,
|
|
588
|
+
)
|
|
448
589
|
args = parser.parse_args()
|
|
449
590
|
|
|
450
591
|
if len(sys.argv) == 1 or args.help:
|
|
@@ -455,6 +596,24 @@ def main():
|
|
|
455
596
|
print(f"its-magic v{version}")
|
|
456
597
|
return 0
|
|
457
598
|
|
|
599
|
+
if args.scratchpad_postinstall:
|
|
600
|
+
target_root = normalize(args.target) if args.target else normalize(".")
|
|
601
|
+
mode = args.mode or "missing"
|
|
602
|
+
if mode not in ("missing", "overwrite", "interactive", "upgrade"):
|
|
603
|
+
print(
|
|
604
|
+
"[SCRATCHPAD_POSTINSTALL_ERROR] INVALID_MODE: use --mode "
|
|
605
|
+
"missing|overwrite|interactive|upgrade with --scratchpad-postinstall."
|
|
606
|
+
)
|
|
607
|
+
return 1
|
|
608
|
+
if not os.path.isdir(source_root):
|
|
609
|
+
print("[INSTALL_SOURCE_ERROR] template directory is missing. Reinstall its-magic package.")
|
|
610
|
+
return 1
|
|
611
|
+
if not os.path.isdir(target_root):
|
|
612
|
+
print(f"[SCRATCHPAD_POSTINSTALL_ERROR] TARGET_MISSING: {target_root}")
|
|
613
|
+
return 1
|
|
614
|
+
ok = run_scratchpad_postinstall(target_root, source_root, mode, print_ok=True)
|
|
615
|
+
return 0 if ok else 1
|
|
616
|
+
|
|
458
617
|
if not os.path.isdir(source_root):
|
|
459
618
|
print("[INSTALL_SOURCE_ERROR] template directory is missing. Reinstall its-magic package.")
|
|
460
619
|
return 1
|
|
@@ -559,6 +718,9 @@ def main():
|
|
|
559
718
|
review.append(rel)
|
|
560
719
|
continue
|
|
561
720
|
|
|
721
|
+
if not run_scratchpad_postinstall(target_root, source_root, "upgrade", print_ok=True):
|
|
722
|
+
return 1
|
|
723
|
+
|
|
562
724
|
write_installed_version(target_root, version)
|
|
563
725
|
sync_root_readme_to_its_magic(target_root)
|
|
564
726
|
runbook_ok, runbook_notes = bootstrap_runbook_commands(target_root)
|
|
@@ -630,6 +792,9 @@ def main():
|
|
|
630
792
|
ensure_parent(dst)
|
|
631
793
|
shutil.copy2(src, dst)
|
|
632
794
|
|
|
795
|
+
if not run_scratchpad_postinstall(target_root, source_root, mode, print_ok=True):
|
|
796
|
+
return 1
|
|
797
|
+
|
|
633
798
|
write_installed_version(target_root, version)
|
|
634
799
|
sync_root_readme_to_its_magic(target_root)
|
|
635
800
|
runbook_ok, runbook_notes = bootstrap_runbook_commands(target_root)
|
package/installer.sh
CHANGED
|
@@ -46,7 +46,10 @@ show_help() {
|
|
|
46
46
|
printf " --create Create the target directory if it does not exist.\n\n"
|
|
47
47
|
printf " Note: installer bootstraps runbook TEST/LINT/TYPECHECK commands from\n"
|
|
48
48
|
printf " OS+stack detection; unresolved TEST_COMMAND fails fast with\n"
|
|
49
|
-
printf " [RUNBOOK_BOOTSTRAP_ERROR] diagnostics.\n
|
|
49
|
+
printf " [RUNBOOK_BOOTSTRAP_ERROR] diagnostics.\n"
|
|
50
|
+
printf " Note: scratchpad Model B: .cursor/scratchpad.md is\n"
|
|
51
|
+
printf " materialized when missing; Python 3 on PATH is required for validation.\n"
|
|
52
|
+
printf " Recovery: python installer.py --scratchpad-postinstall --target <repo> --mode missing\n\n"
|
|
50
53
|
printf "Clean options:\n"
|
|
51
54
|
printf " --clean-repo Remove all its-magic workflow artifacts from the target repo\n"
|
|
52
55
|
printf " (owned paths from installer manifest, including .cursor,\n"
|
|
@@ -130,10 +133,28 @@ choose_mode() {
|
|
|
130
133
|
esac
|
|
131
134
|
}
|
|
132
135
|
|
|
136
|
+
scratchpad_postinstall() {
|
|
137
|
+
target_root="$1"
|
|
138
|
+
mode="$2"
|
|
139
|
+
installer_py="$SCRIPT_DIR/installer.py"
|
|
140
|
+
if [ ! -f "$installer_py" ]; then
|
|
141
|
+
printf "%s\n" "[SCRATCHPAD_POSTINSTALL_ERROR] installer.py missing next to installer.sh."
|
|
142
|
+
exit 1
|
|
143
|
+
fi
|
|
144
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
145
|
+
python3 "$installer_py" --scratchpad-postinstall --target "$target_root" --mode "$mode" || exit $?
|
|
146
|
+
elif command -v python >/dev/null 2>&1; then
|
|
147
|
+
python "$installer_py" --scratchpad-postinstall --target "$target_root" --mode "$mode" || exit $?
|
|
148
|
+
else
|
|
149
|
+
printf "%s\n" "[SCRATCHPAD_POSTINSTALL_ERROR] PYTHON_NOT_FOUND: Python 3 is required for scratchpad materialization/validation (Model B)."
|
|
150
|
+
exit 1
|
|
151
|
+
fi
|
|
152
|
+
}
|
|
153
|
+
|
|
133
154
|
classify_file() {
|
|
134
155
|
rel="$1"
|
|
135
156
|
case "$rel" in
|
|
136
|
-
|
|
157
|
+
README.md) echo "mixed" ;;
|
|
137
158
|
.cursor/commands/*|.cursor/rules/*|.cursor/agents/*|.cursor/skills/*) echo "framework" ;;
|
|
138
159
|
.cursor/hooks/*|.cursor/hooks.json|.cursor/scratchpad.local.example.md) echo "framework" ;;
|
|
139
160
|
.github/workflows/*|scripts/validate-and-push*|docs/engineering/context/*|its_magic/*) echo "framework" ;;
|
|
@@ -513,6 +534,8 @@ if [ "$MODE" = "upgrade" ]; then
|
|
|
513
534
|
fi
|
|
514
535
|
done
|
|
515
536
|
|
|
537
|
+
scratchpad_postinstall "$TARGET_ROOT" "upgrade"
|
|
538
|
+
|
|
516
539
|
write_installed_version "$TARGET_ROOT" "$APP_VERSION"
|
|
517
540
|
sync_root_readme_to_its_magic "$TARGET_ROOT" || true
|
|
518
541
|
bootstrap_runbook_commands "$TARGET_ROOT"
|
|
@@ -581,6 +604,8 @@ for rel in $FILES; do
|
|
|
581
604
|
fi
|
|
582
605
|
done
|
|
583
606
|
|
|
607
|
+
scratchpad_postinstall "$TARGET_ROOT" "$MODE"
|
|
608
|
+
|
|
584
609
|
write_installed_version "$TARGET_ROOT" "$APP_VERSION"
|
|
585
610
|
sync_root_readme_to_its_magic "$TARGET_ROOT" || true
|
|
586
611
|
bootstrap_runbook_commands "$TARGET_ROOT"
|
package/package.json
CHANGED
|
@@ -78,6 +78,14 @@ description: "its-magic architecture: define approach, risks, and decisions."
|
|
|
78
78
|
- If `USER_GUIDE_MODE=0`, add no required user-guide steps or blocking checks (zero overhead).
|
|
79
79
|
- If `USER_GUIDE_MODE=1`, reference canonical user-guide path and schema in
|
|
80
80
|
architecture/state for in-scope feature stories; see runbook user-guide section.
|
|
81
|
+
9. Triad hot-surface gate (DEC-0054) when `docs/engineering/architecture.md` is
|
|
82
|
+
mutated:
|
|
83
|
+
- run `python scripts/enforce-triad-hot-surface.py --rollover` then `--check`
|
|
84
|
+
from repository root,
|
|
85
|
+
- on failure stop with `STATE_ARCHIVE_REQUIRED` or
|
|
86
|
+
`ARTIFACT_HOT_SURFACE_OVERSIZE`,
|
|
87
|
+
- preserve non-target history in archive packs only (never delete unrelated
|
|
88
|
+
story sections without archival evidence).
|
|
81
89
|
|
|
82
90
|
## Cross-phase ownership guard (US-0061 / DEC-0043)
|
|
83
91
|
|