its-magic 0.1.2-38 → 0.1.2-40
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.sh +643 -640
- package/package.json +5 -1
- package/scripts/check_intake_template_parity.py +1 -0
- package/scripts/guard_installer_publish.py +70 -0
- package/scripts/intake_evidence_lib.py +100 -1
- package/scripts/remote_config_summary.py +243 -0
- package/template/.cursor/commands/auto.md +20 -2
- package/template/.cursor/commands/execute.md +6 -0
- package/template/.cursor/commands/intake.md +29 -2
- package/template/.cursor/commands/qa.md +5 -0
- package/template/.cursor/scratchpad.md +7 -3
- package/template/docs/engineering/artifact-ownership-policy.md +1 -1
- package/template/docs/engineering/context/installer-owned-paths.manifest +6 -0
- package/template/docs/engineering/runbook.md +31 -0
- package/template/docs/engineering/runtime-connectivity.md +20 -0
- package/template/docs/engineering/us-0084-remote-e2e.md +39 -0
- package/template/scripts/check_intake_template_parity.py +1 -0
- package/template/scripts/guard_installer_publish.py +70 -0
- package/template/scripts/intake_bug_resume_brief_refresh.py +303 -0
- package/template/scripts/intake_evidence_lib.py +100 -1
- package/template/scripts/remote_config_summary.py +243 -0
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-40",
|
|
4
4
|
"description": "its-magic - AI dev team workflow for Cursor.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -17,10 +17,14 @@
|
|
|
17
17
|
"scripts/intake_bug_routing_guard.py",
|
|
18
18
|
"scripts/check_intake_template_parity.py",
|
|
19
19
|
"scripts/materialize_codebase_map.py",
|
|
20
|
+
"scripts/remote_config_summary.py",
|
|
21
|
+
"scripts/guard_installer_publish.py",
|
|
20
22
|
"bin/its-magic.js",
|
|
21
23
|
"bin/postinstall.js"
|
|
22
24
|
],
|
|
23
25
|
"scripts": {
|
|
26
|
+
"guard:installer": "python scripts/guard_installer_publish.py",
|
|
27
|
+
"prepublishOnly": "npm run guard:installer",
|
|
24
28
|
"postinstall": "node bin/postinstall.js",
|
|
25
29
|
"release:patch": "npm version patch && npm publish",
|
|
26
30
|
"release:minor": "npm version minor && npm publish",
|
|
@@ -12,6 +12,7 @@ INTAKE_TEMPLATE_PAIRS: tuple[tuple[str, str], ...] = (
|
|
|
12
12
|
("scripts/intake_evidence_validate.py", "template/scripts/intake_evidence_validate.py"),
|
|
13
13
|
("scripts/intake_evidence_lib.py", "template/scripts/intake_evidence_lib.py"),
|
|
14
14
|
("scripts/intake_bug_routing_guard.py", "template/scripts/intake_bug_routing_guard.py"),
|
|
15
|
+
("scripts/intake_bug_resume_brief_refresh.py", "template/scripts/intake_bug_resume_brief_refresh.py"),
|
|
15
16
|
("scripts/check_intake_template_parity.py", "template/scripts/check_intake_template_parity.py"),
|
|
16
17
|
)
|
|
17
18
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Prepublish / CI guard: installer.sh LF + POSIX-safe startup tokens (US-0084 / AC-2)."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
12
|
+
INSTALLER_SH = ROOT / "installer.sh"
|
|
13
|
+
|
|
14
|
+
FORBIDDEN_TOKENS = (
|
|
15
|
+
"set -euo",
|
|
16
|
+
"set -o pipefail",
|
|
17
|
+
"set -eu -o pipefail",
|
|
18
|
+
"set -o errexit",
|
|
19
|
+
"set -o nounset",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main() -> int:
|
|
24
|
+
if not INSTALLER_SH.is_file():
|
|
25
|
+
print("guard_installer_publish: installer.sh missing", file=sys.stderr)
|
|
26
|
+
return 1
|
|
27
|
+
data = INSTALLER_SH.read_bytes()
|
|
28
|
+
if b"\r" in data:
|
|
29
|
+
print(
|
|
30
|
+
"guard_installer_publish: CR/LF (\\r) bytes found in installer.sh — "
|
|
31
|
+
"use LF only; see docs/engineering/runbook.md (US-0084).",
|
|
32
|
+
file=sys.stderr,
|
|
33
|
+
)
|
|
34
|
+
return 1
|
|
35
|
+
text = data.decode("utf-8", errors="replace")
|
|
36
|
+
for token in FORBIDDEN_TOKENS:
|
|
37
|
+
if token in text:
|
|
38
|
+
print(
|
|
39
|
+
f"guard_installer_publish: forbidden startup token {token!r} in installer.sh",
|
|
40
|
+
file=sys.stderr,
|
|
41
|
+
)
|
|
42
|
+
return 1
|
|
43
|
+
dash = shutil.which("dash")
|
|
44
|
+
if dash:
|
|
45
|
+
r = subprocess.run(
|
|
46
|
+
[dash, "-n", str(INSTALLER_SH)],
|
|
47
|
+
cwd=ROOT,
|
|
48
|
+
capture_output=True,
|
|
49
|
+
text=True,
|
|
50
|
+
encoding="utf-8",
|
|
51
|
+
errors="replace",
|
|
52
|
+
)
|
|
53
|
+
if r.returncode != 0:
|
|
54
|
+
print(
|
|
55
|
+
"guard_installer_publish: dash -n installer.sh failed:\n"
|
|
56
|
+
+ (r.stderr or r.stdout or ""),
|
|
57
|
+
file=sys.stderr,
|
|
58
|
+
)
|
|
59
|
+
return 1
|
|
60
|
+
else:
|
|
61
|
+
print(
|
|
62
|
+
"guard_installer_publish: dash not on PATH; skipping dash -n "
|
|
63
|
+
"(Python CRLF + token checks still enforced).",
|
|
64
|
+
file=sys.stderr,
|
|
65
|
+
)
|
|
66
|
+
return 0
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == "__main__":
|
|
70
|
+
raise SystemExit(main())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Deterministic intake evidence validation
|
|
3
|
-
(US-0078 / US-0083 / DEC-0060 / DEC-0067 / R-0055).
|
|
3
|
+
(US-0078 / US-0083 / DEC-0060 / DEC-0067 / R-0055 / BUG-0007 / R-0066).
|
|
4
4
|
|
|
5
5
|
Consumes a logical intake_evidence bundle (dict). PO workflows MUST run this
|
|
6
6
|
gate before mutating backlog/acceptance; failures are fail-closed.
|
|
@@ -202,6 +202,77 @@ def _row_uses_equivalent_evidence(row: dict[str, Any]) -> bool:
|
|
|
202
202
|
return bool(str(row.get("equivalent_evidence_ref") or "").strip())
|
|
203
203
|
|
|
204
204
|
|
|
205
|
+
def _norm_answer_ref_quoted_user_text(value: Any) -> str:
|
|
206
|
+
"""Normalize quoted_user_text for BUG-0007 duplicate detection (DEC-0060 strip parity)."""
|
|
207
|
+
if value is None:
|
|
208
|
+
return ""
|
|
209
|
+
return str(value).strip()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _row_exempt_from_answer_ref_topic_distinctness(row: dict[str, Any]) -> bool:
|
|
213
|
+
"""BUG-0007: alternate satisfaction paths do not participate in answer_ref blob reuse checks."""
|
|
214
|
+
return _row_uses_equivalent_evidence(row)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _validate_answer_ref_topic_distinctness(
|
|
218
|
+
bundle: dict[str, Any],
|
|
219
|
+
required: list[str],
|
|
220
|
+
by_key: dict[str, dict[str, Any]],
|
|
221
|
+
res: ValidationResult,
|
|
222
|
+
) -> None:
|
|
223
|
+
"""
|
|
224
|
+
BUG-0007 / R-0066: distinct required topic_key rows must not reuse the same
|
|
225
|
+
quoted_user_text under satisfied_by=answer_ref (normalized), except exempt rows.
|
|
226
|
+
"""
|
|
227
|
+
norm_to_topics: dict[str, list[str]] = {}
|
|
228
|
+
for k in required:
|
|
229
|
+
row = by_key.get(k)
|
|
230
|
+
if not row:
|
|
231
|
+
continue
|
|
232
|
+
sat = (row.get("satisfied_by") or "").strip()
|
|
233
|
+
if sat != "answer_ref":
|
|
234
|
+
continue
|
|
235
|
+
if _row_exempt_from_answer_ref_topic_distinctness(row):
|
|
236
|
+
continue
|
|
237
|
+
irid = _row_run_id(bundle, row)
|
|
238
|
+
tit = _row_turn(row)
|
|
239
|
+
if irid is None or tit is None:
|
|
240
|
+
continue
|
|
241
|
+
qraw = row.get("quoted_user_text")
|
|
242
|
+
qtxt = "" if qraw is None else str(qraw)
|
|
243
|
+
ref = (row.get("ref") or "").strip()
|
|
244
|
+
if not ref or not verify_ie_ref(
|
|
245
|
+
ref,
|
|
246
|
+
intake_run_id=irid,
|
|
247
|
+
turn_index=int(tit),
|
|
248
|
+
topic_key=k,
|
|
249
|
+
satisfied_by=sat,
|
|
250
|
+
quoted_user_text=qtxt,
|
|
251
|
+
):
|
|
252
|
+
continue
|
|
253
|
+
norm = _norm_answer_ref_quoted_user_text(qraw)
|
|
254
|
+
norm_to_topics.setdefault(norm, []).append(k)
|
|
255
|
+
|
|
256
|
+
dup_groups: list[str] = []
|
|
257
|
+
for norm, topics in norm_to_topics.items():
|
|
258
|
+
uniq = sorted(set(topics))
|
|
259
|
+
if len(uniq) < 2:
|
|
260
|
+
continue
|
|
261
|
+
dup_groups.append("text=" + repr(norm[:120]) + " topics=" + ",".join(uniq))
|
|
262
|
+
|
|
263
|
+
if dup_groups:
|
|
264
|
+
res.ok = False
|
|
265
|
+
res.add_code("INTAKE_ANSWER_REF_NOT_TOPIC_DISTINCT")
|
|
266
|
+
res.diagnostics.append(
|
|
267
|
+
"Remediation: distinct required topics must not reuse the same quoted_user_text "
|
|
268
|
+
"under satisfied_by=answer_ref (BUG-0007 / R-0066). Duplicates: "
|
|
269
|
+
+ "; ".join(dup_groups)
|
|
270
|
+
+ ". Use per-topic answers, or an allowed alternate path "
|
|
271
|
+
"(evidence_source=equivalent_evidence_ref + equivalent_evidence_ref, "
|
|
272
|
+
"delegation_ref per DEC-0067 / US-0083, or assumption_confirmation_ref on the row)."
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
205
276
|
def _candidate_story_ids(bundle: dict[str, Any]) -> set[str]:
|
|
206
277
|
out: set[str] = set()
|
|
207
278
|
|
|
@@ -527,6 +598,8 @@ def validate_intake_evidence(
|
|
|
527
598
|
res.missing_topics.append(k)
|
|
528
599
|
res.missing_topics = sorted(set(res.missing_topics))
|
|
529
600
|
|
|
601
|
+
_validate_answer_ref_topic_distinctness(bundle, required, by_key, res)
|
|
602
|
+
|
|
530
603
|
# US-0081 / DEC-0064: first/new/broad intake requires complete-plan coverage contract.
|
|
531
604
|
if pack == "first-intake-pack":
|
|
532
605
|
_validate_plan_coverage_contract(bundle, res)
|
|
@@ -668,6 +741,32 @@ def self_test() -> None:
|
|
|
668
741
|
assert d0.ok and d1.ok
|
|
669
742
|
assert d0.primary_codes == d1.primary_codes
|
|
670
743
|
|
|
744
|
+
# BUG-0007: same quoted_user_text across multiple answer_ref required topics fails
|
|
745
|
+
dup_txt = "synthetic blob echoed for every topic"
|
|
746
|
+
dup_rows = []
|
|
747
|
+
for i, key in enumerate(small):
|
|
748
|
+
dup_rows.append(
|
|
749
|
+
{
|
|
750
|
+
"topic_key": key,
|
|
751
|
+
"satisfied_by": "answer_ref",
|
|
752
|
+
"quoted_user_text": dup_txt,
|
|
753
|
+
"intake_run_id": rid,
|
|
754
|
+
"turn_index": 300 + i,
|
|
755
|
+
"ref": build_ie_ref(rid, 300 + i, key, "answer_ref", dup_txt),
|
|
756
|
+
}
|
|
757
|
+
)
|
|
758
|
+
dup_bundle = {
|
|
759
|
+
"selected_pack": "small-intake-pack",
|
|
760
|
+
"intake_run_id": rid,
|
|
761
|
+
"asked_topics": list(small),
|
|
762
|
+
"missing_topics": [],
|
|
763
|
+
"assumptions_confirmed": "(none)",
|
|
764
|
+
"topic_coverage": dup_rows,
|
|
765
|
+
}
|
|
766
|
+
dup_res = validate_intake_evidence(dup_bundle)
|
|
767
|
+
assert not dup_res.ok
|
|
768
|
+
assert "INTAKE_ANSWER_REF_NOT_TOPIC_DISTINCT" in dup_res.primary_codes
|
|
769
|
+
|
|
671
770
|
# First-intake full-plan coverage contract (US-0081 / DEC-0064)
|
|
672
771
|
first = PACK_REQUIRED_KEYS["first-intake-pack"]
|
|
673
772
|
first_rows = []
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Summarize .cursor/remote.json for operators (US-0084 / US-0064 alignment).
|
|
4
|
+
|
|
5
|
+
Stdout: non-secret labels and env-reference names only (no key material).
|
|
6
|
+
Stderr: errors and skip reasons.
|
|
7
|
+
|
|
8
|
+
Exit codes:
|
|
9
|
+
0 OK or REMOTE_EXECUTION=0 skip (DEC-0070)
|
|
10
|
+
1 usage / CLI error
|
|
11
|
+
2 config missing or unreadable
|
|
12
|
+
3 invalid JSON
|
|
13
|
+
4 schema / contract mismatch vs runbook remote.json contract
|
|
14
|
+
5 reserved (unused; DEC-0070 maps REMOTE_EXECUTION=0 to 0)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
ENV_VAR_NAME = re.compile(r"^[A-Z][A-Z0-9_]*$")
|
|
28
|
+
ALLOWED_TYPES = frozenset({"docker", "ssh", "vm"})
|
|
29
|
+
AUTH_MODES = frozenset({"none", "env"})
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _truthy_remote_execution(raw: str | None) -> bool:
|
|
33
|
+
if raw is None:
|
|
34
|
+
return False
|
|
35
|
+
return raw.strip() in {"1", "true", "TRUE", "yes", "YES", "on", "ON"}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _parse_args(argv: list[str]) -> tuple[argparse.Namespace, list[str]]:
|
|
39
|
+
p = argparse.ArgumentParser(description=__doc__.split("\n\n")[0], add_help=True)
|
|
40
|
+
p.add_argument(
|
|
41
|
+
"--config",
|
|
42
|
+
default=None,
|
|
43
|
+
help="Path to remote JSON (default: REMOTE_CONFIG env or .cursor/remote.json)",
|
|
44
|
+
)
|
|
45
|
+
return p.parse_known_args(argv)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _config_path(explicit: str | None) -> Path:
|
|
49
|
+
if explicit:
|
|
50
|
+
return Path(explicit).expanduser()
|
|
51
|
+
env = os.environ.get("REMOTE_CONFIG")
|
|
52
|
+
if env:
|
|
53
|
+
return Path(env).expanduser()
|
|
54
|
+
return Path(".cursor/remote.json")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _validate_env_ref(name: str, value: Any, prefix: str) -> str | None:
|
|
58
|
+
if not isinstance(value, str):
|
|
59
|
+
return f"{prefix}: {name} must be a string env-reference name"
|
|
60
|
+
if not ENV_VAR_NAME.match(value):
|
|
61
|
+
return f"{prefix}: {name} must look like an env var name (A-Z_0-9)"
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def validate_and_summarize(path: Path, data: dict[str, Any]) -> tuple[list[str], list[str]]:
|
|
66
|
+
"""Return (stdout_lines, error_messages)."""
|
|
67
|
+
errs: list[str] = []
|
|
68
|
+
out: list[str] = []
|
|
69
|
+
out.append(f"remote_config={path.as_posix()}")
|
|
70
|
+
|
|
71
|
+
if not isinstance(data.get("version"), int):
|
|
72
|
+
errs.append("[REMOTE_CONFIG_ERROR] root.version: expected integer")
|
|
73
|
+
if not isinstance(data.get("defaultTarget"), str) or not data["defaultTarget"]:
|
|
74
|
+
errs.append("[REMOTE_CONFIG_ERROR] root.defaultTarget: expected non-empty string")
|
|
75
|
+
targets = data.get("targets")
|
|
76
|
+
if not isinstance(targets, list) or not targets:
|
|
77
|
+
errs.append("[REMOTE_CONFIG_ERROR] root.targets: expected non-empty array")
|
|
78
|
+
|
|
79
|
+
if errs:
|
|
80
|
+
return out, errs
|
|
81
|
+
|
|
82
|
+
by_id: dict[str, dict[str, Any]] = {}
|
|
83
|
+
for i, t in enumerate(targets):
|
|
84
|
+
prefix = f"targets[{i}]"
|
|
85
|
+
if not isinstance(t, dict):
|
|
86
|
+
errs.append(f"[REMOTE_CONFIG_ERROR] {prefix}: expected object")
|
|
87
|
+
continue
|
|
88
|
+
tid = t.get("id")
|
|
89
|
+
if not isinstance(tid, str) or not tid:
|
|
90
|
+
errs.append(f"[REMOTE_CONFIG_ERROR] {prefix}.id: expected non-empty string")
|
|
91
|
+
ttype = t.get("type")
|
|
92
|
+
if ttype not in ALLOWED_TYPES:
|
|
93
|
+
errs.append(
|
|
94
|
+
f"[REMOTE_CONFIG_ERROR] {prefix}.type: expected one of {sorted(ALLOWED_TYPES)}"
|
|
95
|
+
)
|
|
96
|
+
if not isinstance(t.get("enabled"), bool):
|
|
97
|
+
errs.append(f"[REMOTE_CONFIG_ERROR] {prefix}.enabled: expected boolean")
|
|
98
|
+
if not isinstance(t.get("host"), str) or not t["host"]:
|
|
99
|
+
errs.append(f"[REMOTE_CONFIG_ERROR] {prefix}.host: expected non-empty string")
|
|
100
|
+
port = t.get("port")
|
|
101
|
+
if not isinstance(port, int) or not (1 <= port <= 65535):
|
|
102
|
+
errs.append(f"[REMOTE_CONFIG_ERROR] {prefix}.port: expected integer 1..65535")
|
|
103
|
+
if not isinstance(t.get("workspaceRoot"), str) or not t["workspaceRoot"]:
|
|
104
|
+
errs.append(
|
|
105
|
+
f"[REMOTE_CONFIG_ERROR] {prefix}.workspaceRoot: expected non-empty string"
|
|
106
|
+
)
|
|
107
|
+
auth = t.get("auth")
|
|
108
|
+
if auth is not None:
|
|
109
|
+
if not isinstance(auth, dict):
|
|
110
|
+
errs.append(f"[REMOTE_CONFIG_ERROR] {prefix}.auth: expected object")
|
|
111
|
+
else:
|
|
112
|
+
mode = auth.get("mode")
|
|
113
|
+
if mode not in AUTH_MODES:
|
|
114
|
+
errs.append(
|
|
115
|
+
f"[REMOTE_CONFIG_ERROR] {prefix}.auth.mode: expected one of {sorted(AUTH_MODES)}"
|
|
116
|
+
)
|
|
117
|
+
elif mode == "env":
|
|
118
|
+
env_keys = (
|
|
119
|
+
"tokenEnv",
|
|
120
|
+
"passwordEnv",
|
|
121
|
+
"privateKeyPathEnv",
|
|
122
|
+
"usernameEnv",
|
|
123
|
+
)
|
|
124
|
+
any_ref = False
|
|
125
|
+
for k in env_keys:
|
|
126
|
+
if k not in auth:
|
|
127
|
+
continue
|
|
128
|
+
any_ref = True
|
|
129
|
+
err = _validate_env_ref(k, auth[k], f"{prefix}.auth")
|
|
130
|
+
if err:
|
|
131
|
+
errs.append(f"[REMOTE_CONFIG_ERROR] {err}")
|
|
132
|
+
if not any_ref:
|
|
133
|
+
errs.append(
|
|
134
|
+
f"[REMOTE_CONFIG_ERROR] {prefix}.auth: mode=env requires at least one *Env field"
|
|
135
|
+
)
|
|
136
|
+
if isinstance(tid, str) and tid:
|
|
137
|
+
by_id[tid] = t
|
|
138
|
+
|
|
139
|
+
default = data["defaultTarget"]
|
|
140
|
+
if default not in by_id:
|
|
141
|
+
errs.append(
|
|
142
|
+
f"[REMOTE_CONFIG_ERROR] defaultTarget={default!r} not found in targets[].id"
|
|
143
|
+
)
|
|
144
|
+
elif not by_id[default].get("enabled"):
|
|
145
|
+
errs.append(
|
|
146
|
+
f"[REMOTE_CONFIG_ERROR] defaultTarget={default!r} must reference an enabled target"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
if errs:
|
|
150
|
+
return out, errs
|
|
151
|
+
|
|
152
|
+
out.append(f"defaultTarget={data['defaultTarget']}")
|
|
153
|
+
for t in targets:
|
|
154
|
+
assert isinstance(t, dict)
|
|
155
|
+
tid = t["id"]
|
|
156
|
+
parts = [
|
|
157
|
+
f"id={tid}",
|
|
158
|
+
f"type={t['type']}",
|
|
159
|
+
f"enabled={t['enabled']!s}",
|
|
160
|
+
f"host={t['host']}",
|
|
161
|
+
f"port={t['port']}",
|
|
162
|
+
f"workspaceRoot={t['workspaceRoot']}",
|
|
163
|
+
]
|
|
164
|
+
auth = t.get("auth")
|
|
165
|
+
if isinstance(auth, dict):
|
|
166
|
+
parts.append(f"auth.mode={auth.get('mode')}")
|
|
167
|
+
for k in (
|
|
168
|
+
"tokenEnv",
|
|
169
|
+
"passwordEnv",
|
|
170
|
+
"privateKeyPathEnv",
|
|
171
|
+
"usernameEnv",
|
|
172
|
+
):
|
|
173
|
+
if k in auth and isinstance(auth[k], str):
|
|
174
|
+
parts.append(f"auth.{k}={auth[k]}")
|
|
175
|
+
out.append("target:" + " ".join(parts))
|
|
176
|
+
|
|
177
|
+
return out, []
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def main(argv: list[str] | None = None) -> int:
|
|
181
|
+
args, rest = _parse_args(argv or sys.argv[1:])
|
|
182
|
+
if rest:
|
|
183
|
+
print(
|
|
184
|
+
f"[REMOTE_CONFIG_SUMMARY] unknown arguments: {' '.join(rest)}",
|
|
185
|
+
file=sys.stderr,
|
|
186
|
+
)
|
|
187
|
+
return 1
|
|
188
|
+
if not _truthy_remote_execution(os.environ.get("REMOTE_EXECUTION")):
|
|
189
|
+
print(
|
|
190
|
+
"[REMOTE_CONFIG_SUMMARY] REMOTE_EXECUTION!=1: skip validation/summary "
|
|
191
|
+
"(zero overhead; DEC-0070 / US-0084).",
|
|
192
|
+
file=sys.stderr,
|
|
193
|
+
)
|
|
194
|
+
return 0
|
|
195
|
+
|
|
196
|
+
path = _config_path(args.config)
|
|
197
|
+
if not path.is_file():
|
|
198
|
+
print(
|
|
199
|
+
f"[REMOTE_CONFIG_ERROR] {path}: file missing or unreadable. "
|
|
200
|
+
"Fix: create config or set REMOTE_EXECUTION=0.",
|
|
201
|
+
file=sys.stderr,
|
|
202
|
+
)
|
|
203
|
+
return 2
|
|
204
|
+
try:
|
|
205
|
+
raw = path.read_text(encoding="utf-8")
|
|
206
|
+
except OSError as e:
|
|
207
|
+
print(
|
|
208
|
+
f"[REMOTE_CONFIG_ERROR] {path}: read failed ({e}). Fix: permissions/path.",
|
|
209
|
+
file=sys.stderr,
|
|
210
|
+
)
|
|
211
|
+
return 2
|
|
212
|
+
try:
|
|
213
|
+
data = json.loads(raw)
|
|
214
|
+
except json.JSONDecodeError as e:
|
|
215
|
+
print(
|
|
216
|
+
f"[REMOTE_CONFIG_ERROR] {path}: invalid JSON ({e}). Fix: syntax.",
|
|
217
|
+
file=sys.stderr,
|
|
218
|
+
)
|
|
219
|
+
return 3
|
|
220
|
+
if not isinstance(data, dict):
|
|
221
|
+
print(
|
|
222
|
+
f"[REMOTE_CONFIG_ERROR] {path}: expected JSON object at root.",
|
|
223
|
+
file=sys.stderr,
|
|
224
|
+
)
|
|
225
|
+
return 4
|
|
226
|
+
|
|
227
|
+
lines, errs = validate_and_summarize(path, data)
|
|
228
|
+
if errs:
|
|
229
|
+
for line in errs:
|
|
230
|
+
print(line, file=sys.stderr)
|
|
231
|
+
print(
|
|
232
|
+
f"[REMOTE_CONFIG_ERROR] {path}: contract check failed. "
|
|
233
|
+
"Fix: align with docs/engineering/runbook.md remote contract (US-0084).",
|
|
234
|
+
file=sys.stderr,
|
|
235
|
+
)
|
|
236
|
+
return 4
|
|
237
|
+
for line in lines:
|
|
238
|
+
print(line)
|
|
239
|
+
return 0
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
if __name__ == "__main__":
|
|
243
|
+
raise SystemExit(main())
|
|
@@ -9,12 +9,30 @@ description: "its-magic auto: deterministic continuation orchestrator."
|
|
|
9
9
|
- tech-lead
|
|
10
10
|
|
|
11
11
|
## Execution model
|
|
12
|
-
- `/auto` is
|
|
13
|
-
-
|
|
12
|
+
- `/auto` is a **spawn-only orchestrator**: it schedules materialization, spawns
|
|
13
|
+
fresh **phase-role** subagents, and verifies phase boundaries—it **must not**
|
|
14
|
+
execute lifecycle phase work, perform phase-role duties, or author **phase
|
|
15
|
+
deliverables** in the orchestrator context.
|
|
16
|
+
- For each phase, **spawn a fresh subagent** for that phase’s canonical role;
|
|
17
|
+
phase output must arrive only via artifacts and handoff files (no in-turn
|
|
18
|
+
orchestrator execution of that phase).
|
|
14
19
|
- Phase context transfer happens only through artifacts and handoff files.
|
|
15
20
|
- Scope is process/workflow orchestration only. Do not claim runtime product
|
|
16
21
|
orchestration changes.
|
|
17
22
|
|
|
23
|
+
## Spawn-boundary integrity (BUG-0006)
|
|
24
|
+
|
|
25
|
+
- **Forbidden**: treating the orchestrator turn as the executor of a lifecycle
|
|
26
|
+
phase (for example running **`architecture`**, **`execute`**, **`qa`**, or any
|
|
27
|
+
other **`phase_id`** in the orchestrator instead of spawning the required
|
|
28
|
+
subagent).
|
|
29
|
+
- **Fail fast** with **`AUTO_ORCHESTRATOR_PHASE_EXECUTION`**. **Remediation**:
|
|
30
|
+
stop; spawn a **fresh** subagent for the canonical **`phase_id`** and **`role`**
|
|
31
|
+
per the phase→role matrix (**DEC-0051**); do not merge phase output into
|
|
32
|
+
orchestrator turns. **Distinct from** **`PHASE_CONTEXT_ISOLATION_VIOLATION`**
|
|
33
|
+
(wrong writer / isolation break) and **`RUNTIME_PROOF_*`** / **`PHASE_ROLE_*`**
|
|
34
|
+
families—do not overload those codes for a missing-spawn violation.
|
|
35
|
+
|
|
18
36
|
## Full specification (US-0080 / DEC-0062)
|
|
19
37
|
|
|
20
38
|
Long prose, expanded mode semantics, and **Steps 1–13** detail live in
|
|
@@ -152,6 +152,12 @@ parity for listed paths: **`python scripts/check_token_cost_parity.py --repo .`*
|
|
|
152
152
|
fail fast with `REMOTE_CONNECTIVITY_CONFIG_INVALID`.
|
|
153
153
|
- Never expose secrets in execution outputs; only sanitized endpoint data and
|
|
154
154
|
env-reference names are allowed.
|
|
155
|
+
17b. Remote evidence cues (US-0084):
|
|
156
|
+
- When `REMOTE_EXECUTION=1`, cite an **environment label** in
|
|
157
|
+
`handoffs/dev_to_qa.md` (e.g. `WSL`, `ssh:SSH_HOST` as the **env var name**,
|
|
158
|
+
`dockerOverSsh`) and state **where tests ran** (local vs remote host).
|
|
159
|
+
- Do not paste private keys, tokens, or resolved secret **values**; use
|
|
160
|
+
`python scripts/remote_config_summary.py` for a names-only summary when needed.
|
|
155
161
|
18. Runtime QA autopilot execution contract (US-0065 / DEC-0047):
|
|
156
162
|
- Treat runtime verification as mandatory for generated-project scope; static
|
|
157
163
|
checks alone are not sufficient evidence for PASS readiness.
|
|
@@ -21,6 +21,7 @@ description: "its-magic intake: clarify idea and capture story + acceptance."
|
|
|
21
21
|
- `docs/product/backlog.md`
|
|
22
22
|
- `docs/product/acceptance.md`
|
|
23
23
|
- `handoffs/po_to_tl.md`
|
|
24
|
+
- `handoffs/resume_brief.md` (required on successful **`/intake bug`** persistence — **DEC-0069** / **BUG-0005**)
|
|
24
25
|
- Optional (when enabled): `docs/engineering/compatibility-report.md`
|
|
25
26
|
- Optional (when enabled): `docs/engineering/component-scope.md`
|
|
26
27
|
|
|
@@ -41,7 +42,7 @@ description: "its-magic intake: clarify idea and capture story + acceptance."
|
|
|
41
42
|
- For artifact mutations, enforce deterministic single-writer scope:
|
|
42
43
|
- establish writer identity (`writer_id`) and run identity (`intake_run_id`),
|
|
43
44
|
- bind writes to target artifacts (`backlog`, `acceptance`, `vision`,
|
|
44
|
-
`po_to_tl`) for this run.
|
|
45
|
+
`po_to_tl`, and when persisting bugs: `resume_brief` per **DEC-0069**) for this run.
|
|
45
46
|
- Drift guard semantics:
|
|
46
47
|
- self-write changes from the same `(writer_id, intake_run_id)` are valid and
|
|
47
48
|
must not trigger concurrent-writer blockers,
|
|
@@ -73,6 +74,7 @@ description: "its-magic intake: clarify idea and capture story + acceptance."
|
|
|
73
74
|
- `INTAKE_REQUIRED_TOPIC_MISSING`
|
|
74
75
|
- `INTAKE_REQUIRED_PACK_INCOMPLETE`
|
|
75
76
|
- `INTAKE_ASSUMPTION_CONFIRMATION_REQUIRED`
|
|
77
|
+
- `INTAKE_ANSWER_REF_NOT_TOPIC_DISTINCT` (**BUG-0007** / **R-0066** — see **Truthfulness** below)
|
|
76
78
|
- `INTAKE_PERSISTENCE_BLOCKED`
|
|
77
79
|
- Remediation guidance surface (mandatory on block):
|
|
78
80
|
- list `missing_topics`,
|
|
@@ -127,6 +129,24 @@ and assumption binding. **Do not** mutate `docs/product/backlog.md` or
|
|
|
127
129
|
(**AC-5**, **AC-6**).
|
|
128
130
|
- **Grandfathering**: legacy intake rows remain valid for read/display; the **next** intake-driven
|
|
129
131
|
mutation must supply full **US-0078** evidence or the write is blocked (**DEC-0060** §5).
|
|
132
|
+
- Truthfulness / anti-echo (**BUG-0007** / **R-0066**): `INTAKE_ANSWER_REF_NOT_TOPIC_DISTINCT` when
|
|
133
|
+
the same normalized `quoted_user_text` is reused across distinct required `topic_key` rows under
|
|
134
|
+
`satisfied_by=answer_ref` without an exempt path (`evidence_source=equivalent_evidence_ref` +
|
|
135
|
+
`equivalent_evidence_ref`, `delegation_ref` per **DEC-0067** / **US-0083**, or
|
|
136
|
+
`assumption_confirmation_ref` on the row). Canonical **`ie:`** integrity (**DEC-0060**) does not
|
|
137
|
+
prove a topic was actually elicited.
|
|
138
|
+
|
|
139
|
+
## Truthfulness: `asked_topics` and `topic_coverage` (BUG-0007 / US-0083 / DEC-0060 / DEC-0067)
|
|
140
|
+
|
|
141
|
+
- **`asked_topics`** may list a required `topic_key` only when a **user-visible question** was posed
|
|
142
|
+
**or** a **DEC-0060**-allowed alternate applies: **`delegation_ref`** (**DEC-0067**, **US-0083**),
|
|
143
|
+
**`evidence_source=equivalent_evidence_ref`** with **`equivalent_evidence_ref`**, or
|
|
144
|
+
**`assumption_confirmation_ref`** (row-level and/or bundle-level assumption binding per the gate
|
|
145
|
+
contract above).
|
|
146
|
+
- **Forbidden**: fabricating **`topic_coverage`** by echoing **one** user or bug-report blob into
|
|
147
|
+
**`quoted_user_text`** on **every** required key as **`answer_ref`** solely to satisfy structure.
|
|
148
|
+
The validator rejects that pattern under **`INTAKE_ANSWER_REF_NOT_TOPIC_DISTINCT`** (see
|
|
149
|
+
**`docs/engineering/architecture.md`** **`# BUG-0007`**).
|
|
130
150
|
|
|
131
151
|
## Bug issue routing (US-0079 / DEC-0061)
|
|
132
152
|
|
|
@@ -138,6 +158,12 @@ and assumption binding. **Do not** mutate `docs/product/backlog.md` or
|
|
|
138
158
|
- Append **`### BUG-#### — Title`** under **`## Bug issues (canonical)`** with **Status**, **`environment`**, **`steps_to_reproduce`**, **`expected`**, **`actual`**, **`evidence_refs`**.
|
|
139
159
|
- Add matching **`- [ ]` / `- [x]`** row under **`## Bug acceptance (canonical)`**, sorted by id.
|
|
140
160
|
- Run **`python scripts/bug_issue_validate.py --backlog docs/product/backlog.md --check-acceptance`** before completing the handoff.
|
|
161
|
+
- **Resume brief refresh (DEC-0069 / BUG-0005)**: immediately after successful bug persistence and backlog/acceptance validation **PASS**, run the atomic writer (temp file + replace — idempotent latest-pointer upsert):
|
|
162
|
+
- `python scripts/intake_bug_resume_brief_refresh.py --bug-id BUG-#### --backlog docs/product/backlog.md --resume-brief handoffs/resume_brief.md --intake-boundary-utc <RFC3339Z>`
|
|
163
|
+
- Optional: `--orchestrator-run-id`, `--intake-evidence handoffs/intake_evidence/....json`, `--sprint-id` when known.
|
|
164
|
+
- Exit non-zero → **`INTAKE_RESUME_BRIEF_*`** family — do not claim intake complete; fix backlog/brief contradiction or supply valid boundary UTC.
|
|
165
|
+
- Post-condition: **`intended_resume_phase` / `resolved_start_phase` = `discovery`**, **`resolution_source=resume_brief`**, **`bug_id`** matches persisted row, so **`/auto`** without **`start-from`** does not false-trigger **`RESUME_BRIEF_STALE`** for a stale pre-intake **`intake`** target.
|
|
166
|
+
- Optional audit: `python scripts/intake_bug_resume_brief_refresh.py --bug-id BUG-#### --backlog docs/product/backlog.md --resume-brief handoffs/resume_brief.md --validate-file` (no write).
|
|
141
167
|
|
|
142
168
|
## Steps
|
|
143
169
|
1. Determine intake mode from `.cursor/scratchpad.md`:
|
|
@@ -283,7 +309,8 @@ and assumption binding. **Do not** mutate `docs/product/backlog.md` or
|
|
|
283
309
|
- Intake mutations must also comply with
|
|
284
310
|
`docs/engineering/artifact-ownership-policy.md`.
|
|
285
311
|
- Intake may mutate only intake-owned scopes (`vision`, `backlog`, `acceptance`,
|
|
286
|
-
`po_to_tl
|
|
312
|
+
`po_to_tl`, and **`handoffs/resume_brief.md`** only via the **DEC-0069** bug-intake
|
|
313
|
+
completion path / `intake_bug_resume_brief_refresh.py`) for target story context.
|
|
287
314
|
- Any attempted delete/rewrite of non-intake-owned sections fails closed with
|
|
288
315
|
`PHASE_OWNERSHIP_VIOLATION`.
|
|
289
316
|
- If an override-authorized path is configured for an artifact but required
|
|
@@ -114,6 +114,11 @@ verify no unresolved blockers.
|
|
|
114
114
|
- If remote connectivity config is incomplete for required remote checks,
|
|
115
115
|
mark blocking with deterministic reason code
|
|
116
116
|
`REMOTE_CONNECTIVITY_CONFIG_INVALID`.
|
|
117
|
+
- **US-0084**: when `REMOTE_EXECUTION=1`, expect **`handoffs/dev_to_qa.md`**
|
|
118
|
+
to carry an **environment label** (`WSL`, `ssh:<hostEnv>`, `dockerOverSsh`, …)
|
|
119
|
+
and **test locus** (local vs remote); reject evidence that pastes secret
|
|
120
|
+
values (keys/passwords) — **names-only** refs align with
|
|
121
|
+
`python scripts/remote_config_summary.py` output.
|
|
117
122
|
13. Runtime QA autopilot contract (US-0065 / DEC-0047):
|
|
118
123
|
- Runtime truth path is mandatory for generated-project QA:
|
|
119
124
|
`startup -> readiness/connectivity -> log scan -> bounded retry -> verdict`.
|
|
@@ -101,9 +101,13 @@ SPRINT_BULK_MAX_STORIES=5
|
|
|
101
101
|
SPRINT_BULK_MAX_SPRINTS=3
|
|
102
102
|
SPRINT_BULK_SELECTION=priority_then_backlog_order
|
|
103
103
|
#
|
|
104
|
-
# Remote execution
|
|
105
|
-
# - REMOTE_EXECUTION: 0|1
|
|
106
|
-
# - REMOTE_CONFIG: path to remote
|
|
104
|
+
# Remote execution (US-0084 / US-0064)
|
|
105
|
+
# - REMOTE_EXECUTION: 0|1 — 0 skips remote.json validation (zero overhead; DEC-0070).
|
|
106
|
+
# - REMOTE_CONFIG: path to dev/Cursor remote JSON (default .cursor/remote.json).
|
|
107
|
+
# Release/QA SSH/Docker connectivity fields live in docs/engineering/release-targets.json
|
|
108
|
+
# (ssh-server, dockerOverSsh); map WSL vs SSH vs Docker-over-SSH in
|
|
109
|
+
# docs/engineering/runtime-connectivity.md and docs/engineering/us-0084-remote-e2e.md.
|
|
110
|
+
# - Summary helper (names-only stdout): python scripts/remote_config_summary.py
|
|
107
111
|
REMOTE_EXECUTION=0
|
|
108
112
|
REMOTE_CONFIG=.cursor/remote.json
|
|
109
113
|
#
|
|
@@ -23,7 +23,7 @@ artifacts. Ordering policy and ownership policy are complementary:
|
|
|
23
23
|
| `handoffs/release_queue.md` | target sprint row only | `release` | none |
|
|
24
24
|
| `handoffs/release_notes.md` | latest pointer section | `release`, `refresh-context` | none |
|
|
25
25
|
| `docs/engineering/state.md` | append-bottom checkpoints only | all delivery phases | none |
|
|
26
|
-
| `handoffs/resume_brief.md` | current status/next-actions sections | `pause`, `resume`, `refresh-context`, `release` | none |
|
|
26
|
+
| `handoffs/resume_brief.md` | current status/next-actions sections; latest-pointer upsert on **`/intake bug`** completion (**DEC-0069**) | `intake` (bug persistence path only), `pause`, `resume`, `refresh-context`, `release` | none |
|
|
27
27
|
|
|
28
28
|
## Non-destructive mutation rules
|
|
29
29
|
|
|
@@ -28,6 +28,8 @@ scripts/intake_evidence_lib.py
|
|
|
28
28
|
scripts/intake_bug_routing_guard.py
|
|
29
29
|
scripts/check_intake_template_parity.py
|
|
30
30
|
scripts/materialize_codebase_map.py
|
|
31
|
+
scripts/remote_config_summary.py
|
|
32
|
+
scripts/guard_installer_publish.py
|
|
31
33
|
scripts/enforce-triad-hot-surface.py
|
|
32
34
|
.github/workflows
|
|
33
35
|
README.md
|
|
@@ -52,6 +54,8 @@ scripts/intake_evidence_lib.py
|
|
|
52
54
|
scripts/intake_bug_routing_guard.py
|
|
53
55
|
scripts/check_intake_template_parity.py
|
|
54
56
|
scripts/materialize_codebase_map.py
|
|
57
|
+
scripts/remote_config_summary.py
|
|
58
|
+
scripts/guard_installer_publish.py
|
|
55
59
|
scripts/enforce-triad-hot-surface.py
|
|
56
60
|
.github/workflows/ci.yml
|
|
57
61
|
.github/workflows/deploy.yml
|
|
@@ -69,4 +73,6 @@ scripts/intake_evidence_lib.py
|
|
|
69
73
|
scripts/intake_bug_routing_guard.py
|
|
70
74
|
scripts/check_intake_template_parity.py
|
|
71
75
|
scripts/materialize_codebase_map.py
|
|
76
|
+
scripts/remote_config_summary.py
|
|
77
|
+
scripts/guard_installer_publish.py
|
|
72
78
|
scripts/enforce-triad-hot-surface.py
|