runward 0.13.1 → 0.13.3

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 CHANGED
@@ -137,7 +137,9 @@ Read the doctrine: [Concevoir et exécuter des systèmes agentiques (FR)](https:
137
137
 
138
138
  ## Supported tools
139
139
 
140
- Tool profiles: **Claude Code** (slash commands `/rw-*`), **Cursor** (rules), **GitHub Copilot** (instructions), **Gemini CLI** (GEMINI.md), **Windsurf** (rules). `AGENTS.md` is always written it is the vendor-neutral standard read by Codex CLI, opencode, Amp and a growing list of agents, so the method works even without a dedicated profile. The mission structure itself is plain markdown in your repo. More profiles welcome (see [ROADMAP.md](ROADMAP.md)).
140
+ Two vendor-neutral files are **always written**, so the method works on any agent with no profile at all: `AGENTS.md` (the standard charter read by Codex, Cursor, Copilot, Windsurf, Cline, Zed, Amp, opencode, Junie, Warp and more), and the **phase skills** at `.agents/skills/runward-<phase>/SKILL.md` the converged `SKILL.md` seam read by 14+ harnesses, which surface each build phase's craft rules *by relevance* at the point of action (subordinate to the gate: a skill loaded but not applied still fails `check --strict`).
141
+
142
+ Tool profiles (`--tools`) add harness-specific wiring on top: **Claude Code** (`/rw-*` commands + `.claude/skills/`), **Cursor**, **GitHub Copilot**, **Gemini CLI**, **Windsurf** (charter files), **Continue.dev** (`.continue/rules/`), **JetBrains Junie** and **Trae** (`.<harness>/skills/`). The mission structure itself is plain markdown in your repo. More profiles welcome (see [ROADMAP.md](ROADMAP.md)).
141
143
 
142
144
  ## License
143
145
 
@@ -1,6 +1,7 @@
1
1
  import { join, resolve } from "node:path";
2
2
  import { analyze, findMissionRoot } from "../lib/mission.js";
3
3
  import { conformance, driftReport, unratifiedAdrs, decisionCoverage } from "../lib/conformance.js";
4
+ import { behavioralProof } from "../lib/behavioral-proof.js";
4
5
  import { runHooks } from "../lib/hooks.js";
5
6
  import { c, createHeader, section, status } from "../lib/styles.js";
6
7
  import { VERSION } from "../lib/paths.js";
@@ -96,6 +97,27 @@ export async function checkCommand(opts) {
96
97
  strictGaps += unratified.length;
97
98
  }
98
99
  if (checked > 0 && strictGaps === 0) {
100
+ // The two proofs, made legible together: this gate is the DOCUMENTARY proof (decisions traced);
101
+ // the BEHAVIORAL proof is the operator's test suite. runward reads the pointer, never runs the code.
102
+ console.log(section("Behavioral proof (advisory, above the gate)"));
103
+ console.log(" " + c.darkGray("this gate is the documentary proof: the decisions are traced. runward did not run your code — it is not a runtime. The behavioral proof is your test suite."));
104
+ const bp = behavioralProof(mission, root);
105
+ if (!bp.declared) {
106
+ console.log(" " + c.darkGray("no behavioral proof declared — add `Behavioral proof: <command>` to runward/floor.md §2 (and optionally `Proof artifact: <path>`)."));
107
+ }
108
+ else {
109
+ if (bp.command)
110
+ console.log(` ${c.darkGray("prove behavior with:")} ${c.white(bp.command)}`);
111
+ if (bp.artifact) {
112
+ if (!bp.present)
113
+ console.log(` ${c.warning("◑")} ${c.darkGray("proof artifact")} ${c.white(bp.artifact)} ${c.warning("missing — run the command to produce it")}`);
114
+ else {
115
+ const fresh = bp.fresh === undefined ? "" : bp.fresh ? c.success(" · fresh") : c.warning(" · stale (older than the code — re-run)");
116
+ console.log(` ${c.success("✓")} ${c.darkGray("proof artifact")} ${c.white(bp.artifact)} ${c.darkGray(`(${bp.date})`)}${fresh}`);
117
+ }
118
+ }
119
+ console.log(" " + c.darkGray("advisory — runward reports presence and freshness, never runs or reads the result. You cross on both proofs."));
120
+ }
99
121
  console.log(section("Semantic check (advisory, above the gate)"));
100
122
  console.log(" " + c.darkGray("the gate proved every CRITICAL/HIGH rule was traced — not that the code applies it. Before you cross, run the verify workflow (runward/workflows/verify.md): an adversarial cite-vs-apply pass, ideally on a different model. Advisory, agent-executed, never blocks the gate (ADR-0007)."));
101
123
  }
@@ -2,7 +2,7 @@ import { join, resolve } from "node:path";
2
2
  import { readdirSync } from "node:fs";
3
3
  import { checkbox, input, select } from "@inquirer/prompts";
4
4
  import { TEMPLATES, EXAMPLE_MISSION, MISSION_LAYOUT, VERSION } from "../lib/paths.js";
5
- import { TOOL_PROFILES, TOOL_IDS } from "../lib/tools.js";
5
+ import { TOOL_PROFILES, TOOL_IDS, baselineSkills } from "../lib/tools.js";
6
6
  import { makeWriter } from "../lib/write.js";
7
7
  import { c, createHeader, isNonInteractive, section, status } from "../lib/styles.js";
8
8
  /** Recursively copy a shipped directory tree (used to lay down the filled reference mission). */
@@ -100,6 +100,10 @@ export async function initCommand(opts) {
100
100
  }
101
101
  console.log(section("Agent charter"));
102
102
  w.copy(join(TEMPLATES, "targets", "AGENTS.md"), join(root, "AGENTS.md"));
103
+ // Vendor-neutral phase skills (ADR-0018): the converged SKILL.md alias read by 14+ harnesses.
104
+ console.log(section("Phase skills (.agents/skills — vendor-neutral, relevance-loaded)"));
105
+ for (const f of baselineSkills(root))
106
+ w.write(f.path, f.content);
103
107
  for (const profile of TOOL_PROFILES.filter((p) => tools.includes(p.id))) {
104
108
  console.log(section(`Profile · ${profile.label}`));
105
109
  for (const f of profile.files(root))
@@ -0,0 +1,46 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ export function behavioralProof(mission, root) {
4
+ const floor = join(mission, "floor.md");
5
+ if (!existsSync(floor))
6
+ return { declared: false };
7
+ const text = readFileSync(floor, "utf8");
8
+ const cmd = text.match(/^\s*[*_]*Behavioral proof[*_]*\s*:\s*`?([^`\n]+?)`?\s*$/mi);
9
+ const art = text.match(/^\s*[*_]*Proof artifact[*_]*\s*:\s*`?([^`\n]+?)`?\s*$/mi);
10
+ if (!cmd && !art)
11
+ return { declared: false };
12
+ const proof = { declared: true, command: cmd?.[1]?.trim(), artifact: art?.[1]?.trim() };
13
+ if (proof.artifact) {
14
+ const abs = join(root, proof.artifact);
15
+ proof.present = existsSync(abs);
16
+ if (proof.present) {
17
+ const st = statSync(abs);
18
+ proof.date = st.mtime.toISOString().slice(0, 10);
19
+ const codeDir = join(root, "code");
20
+ const newest = existsSync(codeDir) ? newestSourceMtime(codeDir) : 0;
21
+ proof.fresh = newest === 0 ? undefined : st.mtimeMs >= newest;
22
+ }
23
+ }
24
+ return proof;
25
+ }
26
+ /** Newest mtime of a tracked source file under a dir — read-only, node_modules and dotfiles skipped. */
27
+ function newestSourceMtime(dir) {
28
+ let newest = 0;
29
+ let entries;
30
+ try {
31
+ entries = readdirSync(dir, { withFileTypes: true });
32
+ }
33
+ catch {
34
+ return 0;
35
+ }
36
+ for (const e of entries) {
37
+ if (e.name === "node_modules" || e.name.startsWith("."))
38
+ continue;
39
+ const p = join(dir, e.name);
40
+ if (e.isDirectory())
41
+ newest = Math.max(newest, newestSourceMtime(p));
42
+ else if (/\.(ts|tsx|js|jsx|py|go|rs|java|rb)$/.test(e.name))
43
+ newest = Math.max(newest, statSync(p).mtimeMs);
44
+ }
45
+ return newest;
46
+ }
package/dist/lib/tools.js CHANGED
@@ -17,11 +17,61 @@ const rulesBody = [
17
17
  "with a dated re-evaluation trigger. Complexity is added only on an objective trigger — no trigger, no change.",
18
18
  "",
19
19
  ].join("\n");
20
+ const PHASE_SKILLS = [
21
+ {
22
+ phase: "architect", label: "architecture",
23
+ when: "posing the architecture of an agentic system: domain ports, the model boundary, the integration protocol, or any structural design decision taken before the stack",
24
+ },
25
+ {
26
+ phase: "topology", label: "execution topology",
27
+ when: "deciding where each port's adapter runs and under which data sovereignty: placement family, the secrets/network boundary, or third-party trace export",
28
+ },
29
+ {
30
+ phase: "floor", label: "the executable floor",
31
+ when: "building the smallest executable floor: the single orchestrator, the model port, deterministic guards, persistence, or baseline observability",
32
+ },
33
+ {
34
+ phase: "govern", label: "governance",
35
+ when: "governing an agentic system: the threat model, prompt-injection defense, evaluation, observability, resilience, or any sensitive-action approval",
36
+ },
37
+ ];
38
+ /** The shared body — identical guidance across harnesses; only the packaging frontmatter differs. */
39
+ const skillBody = (s) => [
40
+ `# Runward — ${s.label} craft`,
41
+ "",
42
+ `Use this when ${s.when}.`,
43
+ "",
44
+ `Confront the CRITICAL/HIGH craft rules mapped to the \`${s.phase}\` phase in \`runward/rules/\` — each rule's \`phases:\` frontmatter declares where it applies — at the point of action, not from memory. Account for each in the deliverable's \`## Rule conformance\` manifest: \`applied\` with a \`file:line\` or test, \`deviated\` with an ADR, or \`n/a\` with a reason.`,
45
+ "",
46
+ "This skill helps you *apply* the rules; it does not enforce them. `runward check --strict` is the sole authority and verifies the manifest deterministically. A rule surfaced here but not accounted for still fails the gate.",
47
+ "",
48
+ ].join("\n");
49
+ /** SKILL.md open-format: name + description (the relevance trigger) + body. */
50
+ const skillMd = (s) => [
51
+ "---",
52
+ `name: runward-${s.phase}`,
53
+ `description: Runward ${s.label} craft rules. Use when ${s.when}.`,
54
+ "---",
55
+ "",
56
+ skillBody(s),
57
+ ].join("\n");
58
+ /** Emit the four phase skills as SKILL.md folders under `<root>/<...dir>/runward-<phase>/SKILL.md`. */
59
+ const skillsAt = (root, ...dir) => PHASE_SKILLS.map((s) => ({ path: join(root, ...dir, `runward-${s.phase}`, "SKILL.md"), content: skillMd(s) }));
60
+ /**
61
+ * The vendor-neutral phase skills at `.agents/skills/` — the converged SKILL.md alias read by
62
+ * 14+ harnesses, no agent privileged. Always written alongside AGENTS.md, like the rules.
63
+ */
64
+ export function baselineSkills(root) {
65
+ return skillsAt(root, ".agents", "skills");
66
+ }
20
67
  export const TOOL_PROFILES = [
21
68
  {
22
69
  id: "claude",
23
- label: "Claude Code (.claude/commands/rw-*)",
24
- files: (root) => WORKFLOWS.map((wf) => ({ path: join(root, ".claude", "commands", `rw-${wf}.md`), content: pointer(wf) })),
70
+ label: "Claude Code (.claude/commands/rw-* + .claude/skills/)",
71
+ files: (root) => [
72
+ ...WORKFLOWS.map((wf) => ({ path: join(root, ".claude", "commands", `rw-${wf}.md`), content: pointer(wf) })),
73
+ ...skillsAt(root, ".claude", "skills"),
74
+ ],
25
75
  },
26
76
  {
27
77
  id: "cursor",
@@ -55,5 +105,25 @@ export const TOOL_PROFILES = [
55
105
  content: "# Runward\n\n" + rulesBody,
56
106
  }],
57
107
  },
108
+ {
109
+ id: "continue",
110
+ label: "Continue.dev (.continue/rules/runward-*)",
111
+ files: (root) => PHASE_SKILLS.map((s) => ({
112
+ path: join(root, ".continue", "rules", `runward-${s.phase}.md`),
113
+ content: ["---", `name: Runward ${s.label} craft`, `description: Use when ${s.when}.`, "alwaysApply: false", "---", "", skillBody(s)].join("\n"),
114
+ })),
115
+ },
116
+ {
117
+ id: "junie",
118
+ label: "JetBrains Junie (.junie/skills/)",
119
+ files: (root) => skillsAt(root, ".junie", "skills"),
120
+ },
121
+ {
122
+ id: "trae",
123
+ label: "Trae (.trae/skills/)",
124
+ files: (root) => skillsAt(root, ".trae", "skills"),
125
+ },
58
126
  ];
59
127
  export const TOOL_IDS = TOOL_PROFILES.map((t) => t.id);
128
+ /** The gated build phases that get a relevance-loaded phase skill (ADR-0018). */
129
+ export const SKILL_PHASES = PHASE_SKILLS.map((s) => s.phase);
@@ -37,6 +37,10 @@
37
37
  - **Verdict**: criterion met on the replayed sample and the first live week. The gate to `iterate` requires the full two-week live window per the criterion's wording — one more week of live measurement before the gate is crossed. Partial by duration, not by result.
38
38
  - **Observability check**: confirmed — a full trajectory (intake, model proposal, per-field guard outcome, routing decision, persistence) reconstructs from a single request ID; verified on 10 randomly drawn requests.
39
39
 
40
+ **Behavioral proof**: `cd code && npm test`
41
+
42
+ > 14 deterministic tests, no key, no network — including the guard refusing, fail-closed, a record whose action-bearing fields are still model-proposed (ADR-0002). The gate above proves the *decision* to guard was traced; this proves the guard *runs*. runward reports the pointer, never runs it.
43
+
40
44
  ## 3. Gaps and deviations
41
45
 
42
46
  | Gap / deviation | Impact | Agreed with sponsor |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runward",
3
- "version": "0.13.1",
3
+ "version": "0.13.3",
4
4
  "description": "After the spec: ship and run. A delivery framework for agentic systems — floor first, evolution on evidence, governance from day zero, handover with proof.",
5
5
  "keywords": [
6
6
  "agentic",
@@ -34,6 +34,11 @@
34
34
  - **Verdict**: [criterion met / partially met / not met — and what that means for the gate.]
35
35
  - **Observability check**: [confirmation that a full trajectory reconstructs from a single request ID.]
36
36
 
37
+ **Behavioral proof**: `[the command that proves the floor's behavior, e.g. cd code && npm test]`
38
+ **Proof artifact**: [optional — a result file the command writes, e.g. code/test-results.json; runward reports it present/fresh, never runs or reads it]
39
+
40
+ > The gate above is the *documentary* proof (the decisions are traced). This line is the *behavioral* proof (your code actually runs). runward never executes it — it is not a runtime; on a green `--strict` it only reports the pointer, and the artifact's presence and freshness if you name one.
41
+
37
42
  ## 3. Gaps and deviations
38
43
 
39
44
  [Anything shipped differently from the architecture note, and anything the measurement revealed. Deviations were agreed with the sponsor, never silent.]