@heretek-ai/epistemic-swarm 0.7.13 → 0.7.14

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.
Files changed (82) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +5 -1
  3. package/package.json +2 -1
  4. package/plugins/factory/skills/factory/scripts/factory.py +68 -14
  5. package/plugins/socratic-grilling/skills/grilling/SKILL.md +14 -1
  6. package/plugins/socratic-grilling/skills/grilling/socratic_tree.py +88 -16
  7. package/runner/__pycache__/__init__.cpython-311.pyc +0 -0
  8. package/runner/__pycache__/auctioneer.cpython-311.pyc +0 -0
  9. package/runner/__pycache__/auditor_engine.cpython-311.pyc +0 -0
  10. package/runner/__pycache__/claim_store.cpython-311.pyc +0 -0
  11. package/runner/__pycache__/claim_witness.cpython-311.pyc +0 -0
  12. package/runner/__pycache__/darkharvest_claims.cpython-311.pyc +0 -0
  13. package/runner/__pycache__/errors.cpython-311.pyc +0 -0
  14. package/runner/__pycache__/living_dossiers.cpython-311.pyc +0 -0
  15. package/runner/__pycache__/mcp_protocol.cpython-311.pyc +0 -0
  16. package/runner/__pycache__/mcp_server.cpython-311.pyc +0 -0
  17. package/runner/__pycache__/path_safety.cpython-311.pyc +0 -0
  18. package/runner/__pycache__/pcrb.cpython-311.pyc +0 -0
  19. package/runner/__pycache__/pcrb_verify.cpython-311.pyc +0 -0
  20. package/runner/__pycache__/preflight.cpython-311.pyc +0 -0
  21. package/runner/__pycache__/refinement.cpython-311.pyc +0 -0
  22. package/runner/__pycache__/research_swarm.cpython-311.pyc +0 -0
  23. package/runner/__pycache__/retrieval_log.cpython-311.pyc +0 -0
  24. package/runner/__pycache__/schema_validate.cpython-311.pyc +0 -0
  25. package/runner/__pycache__/schemas.cpython-311.pyc +0 -0
  26. package/runner/__pycache__/state_machine.cpython-311.pyc +0 -0
  27. package/runner/errors.py +51 -0
  28. package/runner/mcp_server.py +132 -8
  29. package/runner/preflight.py +157 -0
  30. package/runner/research_swarm.py +148 -4
  31. package/runner/schema_validate.py +93 -0
  32. package/runner/schemas.py +270 -0
  33. package/runner/state_machine.py +96 -10
  34. package/runner/tests/__pycache__/test_auction_order.cpython-311.pyc +0 -0
  35. package/runner/tests/__pycache__/test_backends.cpython-311.pyc +0 -0
  36. package/runner/tests/__pycache__/test_bet1_spike.cpython-311.pyc +0 -0
  37. package/runner/tests/__pycache__/test_claim_store.cpython-311.pyc +0 -0
  38. package/runner/tests/__pycache__/test_claim_witness.cpython-311.pyc +0 -0
  39. package/runner/tests/__pycache__/test_claude_plugin.cpython-311.pyc +0 -0
  40. package/runner/tests/__pycache__/test_contract_tooling.cpython-311.pyc +0 -0
  41. package/runner/tests/__pycache__/test_darkharvest_audit.cpython-311.pyc +0 -0
  42. package/runner/tests/__pycache__/test_domain_packs.cpython-311.pyc +0 -0
  43. package/runner/tests/__pycache__/test_dossier_contract.cpython-311.pyc +0 -0
  44. package/runner/tests/__pycache__/test_factory.cpython-311.pyc +0 -0
  45. package/runner/tests/__pycache__/test_fleet_seam.cpython-311.pyc +0 -0
  46. package/runner/tests/__pycache__/test_grounding.cpython-311.pyc +0 -0
  47. package/runner/tests/__pycache__/test_living_dossiers.cpython-311.pyc +0 -0
  48. package/runner/tests/__pycache__/test_manifest_concurrency.cpython-311.pyc +0 -0
  49. package/runner/tests/__pycache__/test_mcp_server.cpython-311.pyc +0 -0
  50. package/runner/tests/__pycache__/test_opencode_ux.cpython-311.pyc +0 -0
  51. package/runner/tests/__pycache__/test_opencode_v2_registrars.cpython-311.pyc +0 -0
  52. package/runner/tests/__pycache__/test_pcrb.cpython-311.pyc +0 -0
  53. package/runner/tests/__pycache__/test_refinement.cpython-311.pyc +0 -0
  54. package/runner/tests/__pycache__/test_swarm.cpython-311.pyc +0 -0
  55. package/runner/tests/__pycache__/test_swarm_config.cpython-311.pyc +0 -0
  56. package/runner/tests/__pycache__/test_sweep_regressions.cpython-311.pyc +0 -0
  57. package/runner/tests/__pycache__/test_webcache.cpython-311.pyc +0 -0
  58. package/runner/tests/test_contract_tooling.py +312 -0
  59. package/runner/tests/test_mcp_server.py +2 -0
  60. package/schemas/alpha_dossier.schema.json +204 -0
  61. package/schemas/audit_report.schema.json +76 -0
  62. package/schemas/beta_dossier.schema.json +214 -0
  63. package/schemas/frontier.schema.json +61 -0
  64. package/schemas/manifest.schema.json +43 -0
  65. package/schemas/scope_manifest.schema.json +47 -0
  66. package/scripts/__pycache__/bet1_advisory_spike.cpython-311.pyc +0 -0
  67. package/scripts/__pycache__/build_adapters.cpython-311.pyc +0 -0
  68. package/scripts/__pycache__/check_docs.cpython-311.pyc +0 -0
  69. package/scripts/__pycache__/divergence_experiment.cpython-311.pyc +0 -0
  70. package/scripts/__pycache__/gen_schemas.cpython-311.pyc +0 -0
  71. package/scripts/check_docs.py +66 -0
  72. package/scripts/gen_schemas.py +69 -0
  73. package/skills/epistemic_search/scripts/__pycache__/search.cpython-311.pyc +0 -0
  74. package/skills/factory/scripts/factory.py +68 -14
  75. package/skills/grilling/SKILL.md +14 -1
  76. package/skills/grilling/__pycache__/__init__.cpython-311.pyc +0 -0
  77. package/skills/grilling/__pycache__/socratic_tree.cpython-311.pyc +0 -0
  78. package/skills/grilling/socratic_tree.py +88 -16
  79. package/skills/research_cache/__pycache__/__init__.cpython-311.pyc +0 -0
  80. package/skills/research_cache/__pycache__/hasher.cpython-311.pyc +0 -0
  81. package/skills/swarm_config/__pycache__/__init__.cpython-311.pyc +0 -0
  82. package/skills/swarm_config/__pycache__/configure.cpython-311.pyc +0 -0
@@ -0,0 +1,214 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "beta_dossier",
4
+ "type": "object",
5
+ "required": [
6
+ "scope_id"
7
+ ],
8
+ "properties": {
9
+ "scope_id": {
10
+ "type": "string"
11
+ },
12
+ "agent": {
13
+ "type": "string"
14
+ },
15
+ "mode": {
16
+ "type": "string"
17
+ },
18
+ "timestamp": {
19
+ "type": "string"
20
+ },
21
+ "negative_knowledge": {
22
+ "type": "array",
23
+ "items": {
24
+ "type": "object",
25
+ "required": [
26
+ "query",
27
+ "finding"
28
+ ],
29
+ "properties": {
30
+ "query": {
31
+ "type": "string"
32
+ },
33
+ "finding": {
34
+ "type": "string"
35
+ }
36
+ }
37
+ }
38
+ },
39
+ "candidate_repositories": {
40
+ "type": "array",
41
+ "items": {
42
+ "type": "object",
43
+ "required": [
44
+ "name"
45
+ ],
46
+ "properties": {
47
+ "repo_id": {
48
+ "type": "string"
49
+ },
50
+ "name": {
51
+ "type": "string"
52
+ },
53
+ "url": {
54
+ "type": "string"
55
+ },
56
+ "source_hash": {
57
+ "type": "string"
58
+ },
59
+ "verbatim_quote": {
60
+ "type": "string"
61
+ },
62
+ "license": {
63
+ "type": "string"
64
+ },
65
+ "license_risk": {
66
+ "type": "string",
67
+ "enum": [
68
+ "SAFE",
69
+ "COPYLEFT_WARNING",
70
+ "PROHIBITIVE"
71
+ ]
72
+ },
73
+ "harvest_policy": {
74
+ "type": "string",
75
+ "enum": [
76
+ "depend-or-vendor",
77
+ "clean-room-rebuild-only"
78
+ ]
79
+ },
80
+ "verdicts": {
81
+ "type": "array",
82
+ "items": {
83
+ "type": "object",
84
+ "required": [
85
+ "feature",
86
+ "verdict"
87
+ ],
88
+ "properties": {
89
+ "feature": {
90
+ "type": "string"
91
+ },
92
+ "verdict": {
93
+ "type": "string",
94
+ "enum": [
95
+ "depend",
96
+ "vendor",
97
+ "clean-room-rebuild",
98
+ "skip"
99
+ ]
100
+ },
101
+ "reason": {
102
+ "type": "string"
103
+ },
104
+ "verbatim_quote": {
105
+ "type": "string"
106
+ },
107
+ "effort": {
108
+ "type": "string"
109
+ },
110
+ "impact": {
111
+ "type": "integer"
112
+ },
113
+ "differentiation": {
114
+ "type": "integer"
115
+ }
116
+ }
117
+ }
118
+ },
119
+ "warnings": {
120
+ "type": "array",
121
+ "items": {
122
+ "type": "string"
123
+ }
124
+ },
125
+ "attribution": {
126
+ "type": "object"
127
+ }
128
+ }
129
+ }
130
+ },
131
+ "falsification_claims": {
132
+ "type": "array",
133
+ "items": {
134
+ "type": "object",
135
+ "required": [
136
+ "claim_id",
137
+ "statement"
138
+ ],
139
+ "properties": {
140
+ "claim_id": {
141
+ "type": "string"
142
+ },
143
+ "tag": {
144
+ "type": "string",
145
+ "enum": [
146
+ "VERIFIED",
147
+ "INFERRED",
148
+ "HYPOTHESIS",
149
+ "NEGATIVE_KNOWLEDGE"
150
+ ]
151
+ },
152
+ "statement": {
153
+ "type": "string"
154
+ },
155
+ "source_hash": {
156
+ "type": "string"
157
+ },
158
+ "source_url": {
159
+ "type": "string"
160
+ },
161
+ "verbatim_quote": {
162
+ "type": "string"
163
+ },
164
+ "severity": {
165
+ "type": "string"
166
+ }
167
+ }
168
+ }
169
+ },
170
+ "methodological_critiques": {
171
+ "type": "array",
172
+ "items": {
173
+ "type": "object",
174
+ "properties": {
175
+ "target_assertion": {
176
+ "type": "string"
177
+ },
178
+ "critique": {
179
+ "type": "string"
180
+ },
181
+ "evidence_hash": {
182
+ "type": "string"
183
+ }
184
+ }
185
+ }
186
+ },
187
+ "hypotheses": {
188
+ "type": "array",
189
+ "items": {
190
+ "type": "object",
191
+ "required": [
192
+ "statement"
193
+ ],
194
+ "properties": {
195
+ "hypothesis_id": {
196
+ "type": "string"
197
+ },
198
+ "claim_id": {
199
+ "type": "string"
200
+ },
201
+ "tag": {
202
+ "type": "string"
203
+ },
204
+ "statement": {
205
+ "type": "string"
206
+ },
207
+ "falsification": {
208
+ "type": "string"
209
+ }
210
+ }
211
+ }
212
+ }
213
+ }
214
+ }
@@ -0,0 +1,61 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "frontier",
4
+ "type": "object",
5
+ "required": [
6
+ "objective"
7
+ ],
8
+ "properties": {
9
+ "objective": {
10
+ "type": "string"
11
+ },
12
+ "is_complete": {
13
+ "type": "boolean"
14
+ },
15
+ "nodes": {
16
+ "type": "object",
17
+ "additionalProperties": {
18
+ "type": "object",
19
+ "required": [
20
+ "node_id",
21
+ "question"
22
+ ],
23
+ "properties": {
24
+ "node_id": {
25
+ "type": "string"
26
+ },
27
+ "title": {
28
+ "type": "string"
29
+ },
30
+ "question": {
31
+ "type": "string"
32
+ },
33
+ "recommended": {
34
+ "type": "string"
35
+ },
36
+ "options": {
37
+ "type": "array",
38
+ "items": {
39
+ "type": "string"
40
+ }
41
+ },
42
+ "prerequisites": {
43
+ "type": "array",
44
+ "items": {
45
+ "type": "string"
46
+ }
47
+ },
48
+ "settled_answer": {
49
+ "type": [
50
+ "string",
51
+ "null"
52
+ ]
53
+ }
54
+ }
55
+ }
56
+ },
57
+ "settled_constraints": {
58
+ "type": "object"
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "manifest",
4
+ "type": "object",
5
+ "required": [
6
+ "session_id",
7
+ "objective",
8
+ "status",
9
+ "scopes"
10
+ ],
11
+ "properties": {
12
+ "session_id": {
13
+ "type": "string"
14
+ },
15
+ "objective": {
16
+ "type": "string"
17
+ },
18
+ "status": {
19
+ "type": "string"
20
+ },
21
+ "created_at": {
22
+ "type": "string"
23
+ },
24
+ "updated_at": {
25
+ "type": "string"
26
+ },
27
+ "plugin_version": {
28
+ "type": "string"
29
+ },
30
+ "backend": {
31
+ "type": "string"
32
+ },
33
+ "preflight": {
34
+ "type": "object"
35
+ },
36
+ "scopes": {
37
+ "type": "array"
38
+ },
39
+ "telemetry": {
40
+ "type": "object"
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "scope_manifest",
4
+ "type": "object",
5
+ "required": [
6
+ "scope_id",
7
+ "status"
8
+ ],
9
+ "properties": {
10
+ "scope_id": {
11
+ "type": "string"
12
+ },
13
+ "title": {
14
+ "type": "string"
15
+ },
16
+ "objective": {
17
+ "type": "string"
18
+ },
19
+ "dependencies": {
20
+ "type": "array",
21
+ "items": {
22
+ "type": "string"
23
+ }
24
+ },
25
+ "status": {
26
+ "type": "string"
27
+ },
28
+ "alpha_completed": {
29
+ "type": "boolean"
30
+ },
31
+ "beta_completed": {
32
+ "type": "boolean"
33
+ },
34
+ "audit_completed": {
35
+ "type": "boolean"
36
+ },
37
+ "created_at": {
38
+ "type": "string"
39
+ },
40
+ "outputs": {
41
+ "type": "array"
42
+ },
43
+ "self_reported_epistemic_audit": {
44
+ "type": "object"
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env python3
2
+ """Fail CI when a documentation reference points at a file that does not exist.
3
+
4
+ Only *repo-relative* paths are checked (a leading known source directory); bare
5
+ names like `manifest.json` or `hasher.py` are workspace-relative and must not be
6
+ flagged. This is the guard for drifts like the recorded
7
+ `skills/grilling/SKILL.md` `--export` reference (issue #6 item 3.11).
8
+ """
9
+
10
+ import re
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ PROJECT_ROOT = Path(__file__).resolve().parent.parent
15
+ DOC_FILES = ["README.md", "AGENTS.md", "MARKETPLACE.md"]
16
+ DOC_FILES += [
17
+ str(p.relative_to(PROJECT_ROOT)) for p in (PROJECT_ROOT / "docs").glob("*.md")
18
+ ]
19
+
20
+ REPO_PREFIXES = (
21
+ "runner/",
22
+ "skills/",
23
+ "scripts/",
24
+ "plugins/",
25
+ "prompts/",
26
+ "config/",
27
+ "schemas/",
28
+ "docs/",
29
+ "extensions/",
30
+ "hooks/",
31
+ ".claude-plugin/",
32
+ ".omp/",
33
+ ".agents/",
34
+ )
35
+ PATH_RE = re.compile(r"`([A-Za-z0-9_./-]+/[A-Za-z0-9_./-]+)`")
36
+
37
+
38
+ def missing_references() -> list:
39
+ missing = []
40
+ for rel in DOC_FILES:
41
+ path = PROJECT_ROOT / rel
42
+ if not path.exists():
43
+ continue
44
+ text = path.read_text(encoding="utf-8")
45
+ for ref in set(PATH_RE.findall(text)):
46
+ if any(ch in ref for ch in "{}*"):
47
+ continue
48
+ if not ref.startswith(REPO_PREFIXES):
49
+ continue
50
+ if not (PROJECT_ROOT / ref.split("#")[0]).exists():
51
+ missing.append((rel, ref))
52
+ return sorted(set(missing))
53
+
54
+
55
+ def main() -> int:
56
+ missing = missing_references()
57
+ if missing:
58
+ for doc, ref in missing:
59
+ print(f"❌ {doc}: references missing path `{ref}`", file=sys.stderr)
60
+ return 1
61
+ print(f"✅ {len(DOC_FILES)} docs: all repo-relative references resolve.")
62
+ return 0
63
+
64
+
65
+ if __name__ == "__main__":
66
+ raise SystemExit(main())
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env python3
2
+ """Render `schemas/*.schema.json` from `runner/schemas.py`.
3
+
4
+ Generated, never hand-edited. `--check` regenerates in memory and diffs against
5
+ disk so CI fails on drift (issue #6 item 2.1).
6
+ """
7
+
8
+ import argparse
9
+ import json
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ PROJECT_ROOT = Path(__file__).resolve().parent.parent
14
+ if str(PROJECT_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(PROJECT_ROOT))
16
+
17
+ from runner.schemas import SCHEMAS # noqa: E402
18
+
19
+ SCHEMA_DIR = PROJECT_ROOT / "schemas"
20
+
21
+
22
+ def render() -> dict:
23
+ return {name: schema for name, schema in SCHEMAS.items()}
24
+
25
+
26
+ def write_all() -> int:
27
+ SCHEMA_DIR.mkdir(parents=True, exist_ok=True)
28
+ for name, schema in SCHEMAS.items():
29
+ (SCHEMA_DIR / f"{name}.schema.json").write_text(
30
+ json.dumps(schema, indent=2) + "\n", encoding="utf-8"
31
+ )
32
+ return len(SCHEMAS)
33
+
34
+
35
+ def check() -> int:
36
+ errors = []
37
+ for name, schema in SCHEMAS.items():
38
+ path = SCHEMA_DIR / f"{name}.schema.json"
39
+ expected = json.dumps(schema, indent=2) + "\n"
40
+ if not path.exists():
41
+ errors.append(f"MISSING schema: {path.relative_to(PROJECT_ROOT)}")
42
+ elif path.read_text(encoding="utf-8") != expected:
43
+ errors.append(f"OUT OF SYNC: {path.relative_to(PROJECT_ROOT)}")
44
+ stray = {p.name for p in SCHEMA_DIR.glob("*.schema.json")} - {
45
+ f"{n}.schema.json" for n in SCHEMAS
46
+ }
47
+ for name in sorted(stray):
48
+ errors.append(f"STRAY schema (delete or add to runner/schemas.py): {name}")
49
+ if errors:
50
+ for e in errors:
51
+ print(f"❌ {e}", file=sys.stderr)
52
+ return 1
53
+ print(f"✅ {len(SCHEMAS)} schemas in sync with runner/schemas.py.")
54
+ return 0
55
+
56
+
57
+ def main() -> int:
58
+ parser = argparse.ArgumentParser(description="Generate IUMBTEMS JSON schemas")
59
+ parser.add_argument("--check", action="store_true", help="fail on drift")
60
+ args = parser.parse_args()
61
+ if args.check:
62
+ return check()
63
+ count = write_all()
64
+ print(f"✅ Wrote {count} schemas to {SCHEMA_DIR.relative_to(PROJECT_ROOT)}/.")
65
+ return 0
66
+
67
+
68
+ if __name__ == "__main__":
69
+ raise SystemExit(main())
@@ -55,7 +55,10 @@ def run_dir(run):
55
55
  def load_state(run):
56
56
  p = run_dir(run) / "state.json"
57
57
  if not p.exists():
58
- raise SystemExit(f"No factory run '{run}'. Run `factory.py init` first.")
58
+ raise SystemExit(
59
+ f"No factory run '{run}'. Pass --run <name> (or run "
60
+ f"`factory.py init --run <name>` first)."
61
+ )
59
62
  return json.loads(p.read_text(encoding="utf-8"))
60
63
 
61
64
 
@@ -82,6 +85,19 @@ def cmd_phase_add(args):
82
85
  state = load_state(args.run)
83
86
  if args.phase in state["phases"]:
84
87
  raise SystemExit(f"Phase '{args.phase}' already exists.")
88
+ # Phase output goes to phase.json — a run-state helper must never clobber the
89
+ # author-written GOAL.md / dossier.json it is meant to track (#6 item 2.3).
90
+ # This check runs BEFORE any state mutation so a refusal leaves no trace.
91
+ phase_dir = ROADMAP_DIR / args.phase
92
+ authored = [
93
+ name for name in ("GOAL.md", "dossier.json") if (phase_dir / name).exists()
94
+ ]
95
+ if authored and not getattr(args, "force", False):
96
+ raise SystemExit(
97
+ f"Refusing to touch author-written file(s) in {phase_dir}: "
98
+ f"{', '.join(authored)}. phase-add now writes phase.json only; "
99
+ f"re-run with --force to overwrite."
100
+ )
85
101
  state["phases"][args.phase] = {
86
102
  "goal": args.goal,
87
103
  "acceptance": [a.strip() for a in args.accept.split(";") if a.strip()],
@@ -90,30 +106,52 @@ def cmd_phase_add(args):
90
106
  "qa_reports": [],
91
107
  }
92
108
  save_state(args.run, state)
93
- # Phase output: GOAL.md + dossier.json skeleton (evidence filled by manager).
94
- phase_dir = ROADMAP_DIR / args.phase
95
109
  phase_dir.mkdir(parents=True, exist_ok=True)
96
- (phase_dir / "GOAL.md").write_text(
97
- f"# {args.phase}: {args.goal}\n\n## Acceptance\n"
98
- + "".join(f"- [ ] {a}\n" for a in state["phases"][args.phase]["acceptance"]),
99
- encoding="utf-8",
100
- )
101
- (phase_dir / "dossier.json").write_text(
110
+ (phase_dir / "phase.json").write_text(
102
111
  json.dumps(
103
112
  {
104
113
  "phase": args.phase,
105
114
  "goal": args.goal,
106
- "evidence": [],
107
115
  "acceptance": state["phases"][args.phase]["acceptance"],
108
- "brief": "",
109
- "verdict": "briefed",
110
- "hashes": [],
116
+ "status": "briefed",
111
117
  },
112
118
  indent=2,
113
119
  ),
114
120
  encoding="utf-8",
115
121
  )
116
- print(f"✅ Phase '{args.phase}' briefed → {phase_dir}")
122
+ print(f"✅ Phase '{args.phase}' briefed → {phase_dir / 'phase.json'}")
123
+
124
+
125
+ def cmd_gate(args):
126
+ """First-class gate bookkeeping (open/settle/approve/waive/escalate/count)."""
127
+ state = load_state(args.run)
128
+ gates = state.setdefault("gates", {})
129
+ gate = gates.setdefault(
130
+ args.phase, {"status": "closed", "cycles": 0, "waivers": [], "history": []}
131
+ )
132
+ action = (args.action or "").lower()
133
+ if action == "open":
134
+ gate["status"] = "open"
135
+ gate["cycles"] = gate.get("cycles", 0) + 1
136
+ state["gate_cycles"] = state.get("gate_cycles", 0) + 1
137
+ elif action == "settle":
138
+ gate["status"] = "settled"
139
+ elif action == "approve":
140
+ gate["status"] = "approved"
141
+ elif action == "waive":
142
+ gate["status"] = "waived"
143
+ gate["waivers"].append(args.reason or "")
144
+ elif action == "escalate":
145
+ gate["status"] = "escalated"
146
+ gate["cycles"] = gate.get("cycles", 0) + 1
147
+ state["gate_cycles"] = state.get("gate_cycles", 0) + 1
148
+ elif action != "count":
149
+ raise SystemExit(
150
+ "gate --action must be one of: open, settle, approve, waive, escalate, count"
151
+ )
152
+ gate["history"].append({"action": action, "at": now(), "reason": args.reason or ""})
153
+ save_state(args.run, state)
154
+ print(json.dumps({"phase": args.phase, "gate": gate}, indent=2))
117
155
 
118
156
 
119
157
  def cmd_qa_record(args):
@@ -211,6 +249,11 @@ def main():
211
249
  p.add_argument("--phase", required=True)
212
250
  p.add_argument("--goal", required=True)
213
251
  p.add_argument("--accept", default="")
252
+ p.add_argument(
253
+ "--force",
254
+ action="store_true",
255
+ help="Overwrite author-written GOAL.md/dossier.json if present",
256
+ )
214
257
  _add_common(p)
215
258
  p = sub.add_parser("qa-record")
216
259
  p.add_argument("--run", required=True)
@@ -227,6 +270,16 @@ def main():
227
270
  p = sub.add_parser("stop")
228
271
  p.add_argument("--run", required=True)
229
272
  _add_common(p)
273
+ p = sub.add_parser("gate")
274
+ p.add_argument("--run", required=True)
275
+ p.add_argument("--phase", required=True)
276
+ p.add_argument(
277
+ "--action",
278
+ required=True,
279
+ choices=["open", "settle", "approve", "waive", "escalate", "count"],
280
+ )
281
+ p.add_argument("--reason", default="")
282
+ _add_common(p)
230
283
 
231
284
  args = ap.parse_args()
232
285
  root = resolve_project_root(args.project_dir)
@@ -238,6 +291,7 @@ def main():
238
291
  "qa-record": cmd_qa_record,
239
292
  "expansion": cmd_expansion,
240
293
  "stop": cmd_stop,
294
+ "gate": cmd_gate,
241
295
  }[args.command](args)
242
296
  sys.exit(code if isinstance(code, int) else 0)
243
297
 
@@ -42,7 +42,20 @@ Always challenge default premises in Round 1:
42
42
 
43
43
  ## 5. FRONTIER RESOLUTION & FREEZING
44
44
 
45
+ The frontier is fully tool-drivable (no hand-written JSON):
46
+
47
+ ```bash
48
+ # author a node (prerequisites gate when it appears on the frontier)
49
+ python3 skills/grilling/socratic_tree.py --add-node --id N1 \
50
+ --question "L1 or L2 for state verification?" --depends-on ""
51
+ # settle a node
52
+ python3 skills/grilling/socratic_tree.py --settle N1 "L1 for finality"
53
+ # inspect / freeze
54
+ python3 skills/grilling/socratic_tree.py --show-frontier
55
+ python3 skills/grilling/socratic_tree.py --export
56
+ ```
57
+
45
58
  When every branch of the design tree has been visited and the frontier is empty:
46
59
  1. Summarize the settled constraints.
47
- 2. Save the settled state to `.research/frontier.json` using `python3 skills/grilling/socratic_tree.py --export`.
60
+ 2. Freeze the settled state to `.research/frontier.json` (`--export`, default path).
48
61
  3. Hand off the settled frontier to the **Swarm Orchestrator** to begin empirical dialectic execution.