runward 0.16.0 → 0.18.0
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 +12 -7
- package/dist/commands/characterize.js +2 -2
- package/dist/commands/check.js +2 -2
- package/dist/commands/compliance.js +2 -2
- package/dist/commands/doctor.js +2 -1
- package/dist/lib/compliance.js +11 -3
- package/dist/lib/constants.js +4 -1
- package/dist/lib/evidence.js +25 -4
- package/dist/lib/styles.js +13 -0
- package/package.json +1 -1
- package/regimes/eu-ai-act@2024-1689.json +3 -3
- package/templates/adapters/README.md +4 -0
- package/templates/adapters/bmad-review-layer.toml +32 -0
package/README.md
CHANGED
|
@@ -74,7 +74,9 @@ New here? Follow [your first mission in 15 minutes](docs/first-mission.md).
|
|
|
74
74
|
|
|
75
75
|
Global flags: `--yes`, `--dry-run`, `--verbose`, `--no-color`. Exit codes: 0 success, 1 gaps/warnings, 2 missing prerequisite.
|
|
76
76
|
|
|
77
|
-
The gate's exit code is a **port**: `runward/adapters/` ships inert sample wiring so the gate runs at each harness's natural moment.
|
|
77
|
+
The gate's exit code is a **port**: `runward/adapters/` ships inert sample wiring so the gate runs at each harness's natural moment.
|
|
78
|
+
|
|
79
|
+
**Install runward from where you already work.** Beyond the copy-in adapters, runward publishes distributable packagings — a **GitHub Action** (`uses: stranxik/runward@<sha>` as a required check, the hard governance gate at merge), a **Claude Code plugin** (`/plugin marketplace add stranxik/runward`), and siblings for Gemini CLI, Codex, Copilot, Cursor and Kiro — each honestly tiered by how hard its gate can block, plus an MCP descriptor for discovery only (an MCP tool is model-controlled — findable, never a gate). The full, honestly-tiered map is [`docs/distribution.md`](docs/distribution.md). The operator installs; none is privileged. The git `pre-commit` and CI adapters are **agent-agnostic** — they gate whatever agent produced the code (Codex, Claude, Cursor, Copilot, Gemini); the Claude Code `Stop`-hook is one example of a per-harness turn-end hook, not a privileged one. You copy an adapter in; runward never installs or runs one for you.
|
|
78
80
|
|
|
79
81
|
`init` creates:
|
|
80
82
|
|
|
@@ -85,6 +87,7 @@ your-project/
|
|
|
85
87
|
│ ├── framing.md # problem, value, observable success criterion, floor vs target
|
|
86
88
|
│ ├── mission-contract.md # one-page steering contract: engagements, DoD, gates
|
|
87
89
|
│ ├── architecture.md # boundaries, ports, integration protocol — stack stays open
|
|
90
|
+
│ ├── execution-topology.md # per-port placement: location family, data class, sovereignty (gated)
|
|
88
91
|
│ ├── decision-matrix.md # 22 arbitrations: one sober default + one explicit trigger each
|
|
89
92
|
│ ├── reference-stack.md # default adapter kit per layer, with evolution triggers
|
|
90
93
|
│ ├── shared-bricks.md # bricks beyond the app: placement families, brick matrix, sovereignty by data class
|
|
@@ -98,7 +101,7 @@ your-project/
|
|
|
98
101
|
│ │ └── observability-schema.md
|
|
99
102
|
│ ├── contracts/ # port contracts (versioned, additive, tolerant reader)
|
|
100
103
|
│ ├── runbook.md # recovery runbook for the team that inherits the system
|
|
101
|
-
│ ├── adapters/ # inert samples that run the gate at each harness seam (git, GitHub/GitLab CI, Claude Code)
|
|
104
|
+
│ ├── adapters/ # inert samples that run the gate at each harness seam (git, GitHub/GitLab CI, Claude Code, Kiro, BMAD)
|
|
102
105
|
│ └── workflows/ # the method, executable by your agent
|
|
103
106
|
└── .claude/ | .cursor/ # tool profiles (--tools)
|
|
104
107
|
```
|
|
@@ -120,17 +123,19 @@ Phase 5 is transverse: it starts at day zero, not after the incident.
|
|
|
120
123
|
|
|
121
124
|
## What makes it different
|
|
122
125
|
|
|
123
|
-
|
|
126
|
+
Four things, and a code-level competitive check (July 2026) confirmed no other scaffold pairs them: a **rule-level gate with verified, sealed evidence**, a **gated hand-over**, and a **published, deterministic OSCAL mapping**. Each exists somewhere in isolation; the combination is runward's ground. The rest of this section is those four and the invariants under them.
|
|
127
|
+
|
|
128
|
+
- **The gate you own — deterministic and zero-LLM.** `runward check --strict` is a non-probabilistic, operator-owned exit-code gate: it cannot be jailbroken by injection (no model in the gate path) and reruns byte-for-byte. Every competitor's *code* check is judged by the model; this one does not depend on an LLM to pass. (Two 2026 signals back the stance: FedRAMP RFC-0024 forbids GenAI-produced factual evidence, and the Delve affair showed why AI-produced audit evidence is not trusted.)
|
|
124
129
|
- **The gate verifies the evidence, not just the row.** A pointer must resolve to real, non-empty content; a symbol or test name must actually be there; a rule can declare the shape its evidence must contain (`signature:` — the founding cited-not-applied incident is now caught deterministically); a stale pointer fails the gate; a sealed gate (`--freeze`) makes later erosion loud. Still bytes, never judgment — and never a model.
|
|
125
130
|
- **runward gates itself.** This repository carries its own `runward/` mission, and CI requires `runward check --strict` green on runward's own code — including under network isolation. The discipline is demonstrated, not claimed.
|
|
126
|
-
- **Audit-ready evidence,
|
|
131
|
+
- **Audit-ready evidence, in a published format.** Craft rules carry an OWASP ASI (Top 10 for Agentic Applications) mapping — a universal, region-agnostic security posture — so the conformance manifest reads as supporting evidence and exports as a machine-readable OSCAL pack. The decision → ADR → manifest → OSCAL chain is a **versioned, citable mini-spec** ([`docs/spec/runward-oscal-mapping.md`](docs/spec/runward-oscal-mapping.md)) anyone can reproduce or critique — the reference implementation of gate-level agentic-delivery evidence, not a black box. Security-only by default; it maps to your regime when you need one — ISO/IEC 42001 (global), NIST AI RMF (US), or the EU AI Act art. 13 technical file (EU) — see [`docs/compliance/`](docs/compliance/). An input to that work — never a conformity assessment, never a claim to certify.
|
|
127
132
|
- **Never a runtime.** Runward writes nothing into `.git/`, installs nothing, and runs nothing of yours — it frames, gates and hands off. Your runtime and model are swappable adapters behind a port; the thing you depend on is never Runward.
|
|
128
133
|
- **Floor, not MVP deck.** The floor is the smallest *running* system that proves value on real traffic. A presentation is not a floor.
|
|
129
134
|
- **Evolution on evidence.** Multi-agent, long-term memory, microservices, a bigger model: each has a sober default and an explicit trigger. No trigger, no complexity. Every switch is an ADR.
|
|
130
135
|
- **Security by architecture, not detection.** Prompt injection is constrained structurally (lethal trifecta, 2-of-3 rule), not filtered heuristically.
|
|
131
|
-
- **Craft rules, not vibes.**
|
|
136
|
+
- **Craft rules, not vibes.** 64 engineering craft rules ship with the mission (memory scoring, tiered retrieval, event sourcing, request-id propagation, multi-provider fallback, cost routing, post-turn pipelines, prompt-injection defenses…) — your agent applies them, `runward check` will not invent them. Code examples follow the reference-stack default (a single language in the core — TypeScript by default, an adapter decision like any other: see the decision matrix). The patterns are the contract; the language is the adapter.
|
|
132
137
|
- **One accountable operator, not a simulated team.** One human owns every gate while the agent executes the workflows — see [the operator role](docs/operator-role.md).
|
|
133
|
-
- **
|
|
138
|
+
- **Hand-over gated on proof, not a folder.** The mission ends when the receiving team redoes a real task without you — and that succession is a gated deliverable (`handover.md`, four handover rules), verified like every other phase. No other scaffold carries the mission past tested code, let alone checks the succession.
|
|
134
139
|
|
|
135
140
|
## The reference floor
|
|
136
141
|
|
|
@@ -146,7 +151,7 @@ Read the doctrine: [Concevoir et exécuter des systèmes agentiques (FR)](https:
|
|
|
146
151
|
|
|
147
152
|
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`).
|
|
148
153
|
|
|
149
|
-
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)).
|
|
154
|
+
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/`), and **Kiro** (`.kiro/steering/`, relevance-loaded). The mission structure itself is plain markdown in your repo. More profiles welcome (see [ROADMAP.md](ROADMAP.md)).
|
|
150
155
|
|
|
151
156
|
## License
|
|
152
157
|
|
|
@@ -2,7 +2,7 @@ import { existsSync, statSync } from "node:fs";
|
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { buildInventory, renderCharacterization, mineDrafts, renderDraft } from "../lib/characterize.js";
|
|
4
4
|
import { makeWriter } from "../lib/write.js";
|
|
5
|
-
import { c, createHeader,
|
|
5
|
+
import { c, createHeader, generationDate, section, status } from "../lib/styles.js";
|
|
6
6
|
import { VERSION } from "../lib/paths.js";
|
|
7
7
|
/**
|
|
8
8
|
* Characterize an existing codebase (ADR-0014) — read-only, deterministic, zero-LLM.
|
|
@@ -22,7 +22,7 @@ export async function characterizeCommand(opts) {
|
|
|
22
22
|
console.log(section("Reading (read-only)"));
|
|
23
23
|
const inv = buildInventory(root);
|
|
24
24
|
const dryRun = process.env.RUNWARD_DRY_RUN === "1";
|
|
25
|
-
const generatedAt =
|
|
25
|
+
const generatedAt = generationDate();
|
|
26
26
|
const md = renderCharacterization(inv, generatedAt);
|
|
27
27
|
// Generated artifact, not mission state: always refresh it (idempotent).
|
|
28
28
|
const w = makeWriter({ force: true, dryRun, root });
|
package/dist/commands/check.js
CHANGED
|
@@ -6,7 +6,7 @@ import { evidenceReport, verifyEvidenceLock, renderEvidenceLock, EVIDENCE_LOCK }
|
|
|
6
6
|
import { behavioralProof } from "../lib/behavioral-proof.js";
|
|
7
7
|
import { verifyFindings, VERIFY_FINDINGS } from "../lib/verify-findings.js";
|
|
8
8
|
import { runHooks } from "../lib/hooks.js";
|
|
9
|
-
import { c, createHeader,
|
|
9
|
+
import { c, createHeader, generationDate, section, status } from "../lib/styles.js";
|
|
10
10
|
import { VERSION } from "../lib/paths.js";
|
|
11
11
|
/**
|
|
12
12
|
* Gate audit — the gap analysis: which deliverable, expected at which
|
|
@@ -195,7 +195,7 @@ export async function checkCommand(opts) {
|
|
|
195
195
|
console.log(" " + status.error("refusing to seal a red gate — a seal certifies a crossing, not a hope. Close the gaps above, then re-run `runward check --freeze`."));
|
|
196
196
|
}
|
|
197
197
|
else {
|
|
198
|
-
const sealedAt =
|
|
198
|
+
const sealedAt = generationDate();
|
|
199
199
|
const content = renderEvidenceLock(mission, sealedAt);
|
|
200
200
|
const count = Object.keys(JSON.parse(content).files).length;
|
|
201
201
|
if (process.env.RUNWARD_DRY_RUN === "1") {
|
|
@@ -3,7 +3,7 @@ import { findMissionRoot } from "../lib/mission.js";
|
|
|
3
3
|
import { gatherComplianceInputs, renderIso42001Readiness, renderNistAiRmf, renderEuAiAct, renderOscal } from "../lib/compliance.js";
|
|
4
4
|
import { loadRegime, regimeLensId } from "../lib/regimes.js";
|
|
5
5
|
import { makeWriter } from "../lib/write.js";
|
|
6
|
-
import { c, createHeader,
|
|
6
|
+
import { c, createHeader, generationDate, section, status } from "../lib/styles.js";
|
|
7
7
|
import { VERSION } from "../lib/paths.js";
|
|
8
8
|
/**
|
|
9
9
|
* Assemble a regime-framed compliance evidence pack (ADR-0016) — deterministic, read-only, zero-LLM,
|
|
@@ -44,7 +44,7 @@ export async function complianceCommand(regime, opts) {
|
|
|
44
44
|
}
|
|
45
45
|
const mission = join(root, "runward");
|
|
46
46
|
const dryRun = process.env.RUNWARD_DRY_RUN === "1";
|
|
47
|
-
const generatedAt =
|
|
47
|
+
const generatedAt = generationDate();
|
|
48
48
|
console.log(section("Assembling (read-only, deterministic)"));
|
|
49
49
|
const inputs = gatherComplianceInputs(mission);
|
|
50
50
|
const md = spec.render(inputs, generatedAt, lens);
|
package/dist/commands/doctor.js
CHANGED
|
@@ -36,7 +36,8 @@ export async function doctorCommand() {
|
|
|
36
36
|
const ruleCount = existsSync(rulesDir) ? readdirSync(rulesDir).filter((f) => f.endsWith(".md")).length : 0;
|
|
37
37
|
ruleCount === EXPECTED_RULES ? ok(`${ruleCount} craft rules`) : fail(`craft rules mismatch: ${ruleCount}/${EXPECTED_RULES}`);
|
|
38
38
|
const adaptersDir = join(TEMPLATES, "adapters");
|
|
39
|
-
|
|
39
|
+
// Count real adapters, not the README — else substituting an adapter for a stray file passes.
|
|
40
|
+
const adapterCount = existsSync(adaptersDir) ? readdirSync(adaptersDir).filter((f) => f !== "README.md").length : 0;
|
|
40
41
|
adapterCount === EXPECTED_ADAPTERS ? ok(`${adapterCount} gate adapters`) : fail(`gate adapters mismatch: ${adapterCount}/${EXPECTED_ADAPTERS}`);
|
|
41
42
|
// Routed-count floor (ADR-0002): the phases: mapping must not be stripped below its pinned minimum.
|
|
42
43
|
const belowFloor = Object.entries(EXPECTED_MAPPED).filter(([phase, floor]) => expectedRules(TEMPLATES, phase).length < floor);
|
package/dist/lib/compliance.js
CHANGED
|
@@ -34,7 +34,9 @@ function readRules(missionDir) {
|
|
|
34
34
|
if (!existsSync(dir))
|
|
35
35
|
return [];
|
|
36
36
|
const out = [];
|
|
37
|
-
|
|
37
|
+
// Sorted: the ASI coverage lists and the OSCAL derive from this order, so it must be deterministic
|
|
38
|
+
// across filesystems (readdirSync order is not guaranteed) for the byte-identity invariant to hold.
|
|
39
|
+
for (const f of readdirSync(dir).sort()) {
|
|
38
40
|
if (!f.endsWith(".md"))
|
|
39
41
|
continue;
|
|
40
42
|
let fm = "";
|
|
@@ -324,9 +326,15 @@ const ASI_CATALOG = "https://genai.owasp.org/resource/owasp-top-10-for-agentic-a
|
|
|
324
326
|
*/
|
|
325
327
|
export function renderOscal(inputs, missionName, generatedAt, lensId) {
|
|
326
328
|
const ns = missionName || "mission";
|
|
329
|
+
// The link points at the readiness draft co-generated with this pack — the one whose lens framed it.
|
|
330
|
+
const regime = lensId ? lensId.split("@")[0] : "iso-42001";
|
|
331
|
+
const readinessHref = `./${regime}-readiness.md`;
|
|
327
332
|
const irs = Object.keys(ASI_LABELS).map((id) => {
|
|
328
333
|
const slugs = inputs.asiCoverage.get(id) ?? [];
|
|
329
|
-
|
|
334
|
+
// Aggregate EVERY manifest row of EVERY rule mapping this ASI, across all gated deliverables —
|
|
335
|
+
// a rule can appear in more than one manifest (spec §3). `find` (first row) made the status depend
|
|
336
|
+
// on deliverable order and could report `implemented` where a later `deviated` row means `partial`.
|
|
337
|
+
const statuses = slugs.flatMap((s) => inputs.conformance.filter((c) => c.rule === s).map((c) => c.status));
|
|
330
338
|
let impl;
|
|
331
339
|
if (slugs.length === 0)
|
|
332
340
|
impl = "planned"; // no rule maps this risk — a gap
|
|
@@ -339,7 +347,7 @@ export function renderOscal(inputs, missionName, generatedAt, lensId) {
|
|
|
339
347
|
"control-id": `asi-${id.slice(3)}`,
|
|
340
348
|
description: `${id} ${ASI_LABELS[id]}. ${slugs.length ? "Addressed by rules: " + slugs.join(", ") + "." : "No rule mapped — gap to assess."}`,
|
|
341
349
|
props: [{ name: "implementation-status", value: impl }],
|
|
342
|
-
links: [{ href:
|
|
350
|
+
links: [{ href: readinessHref, rel: "reference", text: "runward assessment-readiness draft" }],
|
|
343
351
|
};
|
|
344
352
|
});
|
|
345
353
|
const doc = {
|
package/dist/lib/constants.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/** Total number of craft rules shipped under templates/rules/. */
|
|
2
2
|
export const EXPECTED_RULES = 64;
|
|
3
|
-
/** Gate adapters shipped under templates/adapters/ (ADR-0012): one per harness seam
|
|
3
|
+
/** Gate adapters shipped under templates/adapters/ (ADR-0012): one per harness seam. Counts the real
|
|
4
|
+
* adapters only — the port-contract README.md is excluded, so swapping an adapter for a stray file
|
|
5
|
+
* cannot pass the count. pre-commit, github-actions, gitlab-ci, claude-code-settings, kiro-hooks,
|
|
6
|
+
* bmad-review-layer. */
|
|
4
7
|
export const EXPECTED_ADAPTERS = 6;
|
|
5
8
|
/** Routed-count floor: minimum CRITICAL/HIGH rules mapped to each build phase (ADR-0002).
|
|
6
9
|
* Lowering a floor is a deliberate, tracked edit — the `phases:` mapping cannot be silently
|
package/dist/lib/evidence.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
|
-
import { dirname, isAbsolute, join, relative } from "node:path";
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { parseManifest, evidencePathTokens, adrIdExists, GATED_DELIVERABLES } from "./conformance.js";
|
|
5
5
|
const POINTER_PREFIX = /\b(file|test|adr):(\S.*)$/;
|
|
6
6
|
/** Parse the typed pointers out of an Evidence cell. One pointer per `;`-separated segment. */
|
|
@@ -53,16 +53,33 @@ export function parseEvidencePointers(evidence) {
|
|
|
53
53
|
function clean(token) {
|
|
54
54
|
return token.replace(/[),.:]+$/, "");
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* A rule signature is operator-authored data the gate compiles into a RegExp and runs against file
|
|
58
|
+
* content — so a catastrophic-backtracking pattern (nested quantifiers like `(a+)+`) could hang the
|
|
59
|
+
* gate on adversarial input (a self-inflicted DoS in CI). Reject the known-dangerous shape up front,
|
|
60
|
+
* deterministically: a quantifier applied to a group that itself contains a quantifier. This is a
|
|
61
|
+
* conservative screen (it may reject a rare safe pattern — the operator rewrites it), never a promise
|
|
62
|
+
* to catch every pathological regex. Signatures are simple token alternations in practice. See ADR-0020.
|
|
63
|
+
*/
|
|
64
|
+
export function unsafeSignature(source) {
|
|
65
|
+
// group whose body holds a quantifier, immediately followed by another quantifier: (…+…)+ (…*…)* etc.
|
|
66
|
+
return /\((?![?])[^()]*[+*}][^()]*\)[+*{]/.test(source);
|
|
67
|
+
}
|
|
56
68
|
/** The same three resolution bases as the drift pass (ADR-0004). */
|
|
57
69
|
export function resolutionBases(missionDir, deliverable) {
|
|
58
70
|
return [dirname(missionDir), missionDir, dirname(join(missionDir, deliverable))];
|
|
59
71
|
}
|
|
72
|
+
/** Resolve a pointer strictly WITHIN one of the three project bases (ADR-0019): an absolute
|
|
73
|
+
* path, or a `../` that climbs out of every base, is not "evidence in your project" — it is
|
|
74
|
+
* rejected, not resolved. `resolve` normalizes `..`; the containment check is a prefix test on
|
|
75
|
+
* the normalized path (with a separator, so `/a/project-evil` never counts as under `/a/project`). */
|
|
60
76
|
function resolveFile(p, bases) {
|
|
61
77
|
if (isAbsolute(p))
|
|
62
|
-
return
|
|
78
|
+
return null; // an absolute evidence path escapes the project — never valid
|
|
63
79
|
for (const b of bases) {
|
|
64
|
-
const
|
|
65
|
-
|
|
80
|
+
const baseAbs = resolve(b);
|
|
81
|
+
const abs = resolve(baseAbs, p);
|
|
82
|
+
if ((abs === baseAbs || abs.startsWith(baseAbs + sep)) && existsSync(abs))
|
|
66
83
|
return abs;
|
|
67
84
|
}
|
|
68
85
|
return null;
|
|
@@ -139,6 +156,10 @@ export function evidenceReport(missionDir, deliverable, signatures) {
|
|
|
139
156
|
// Signature (ADR-0020): a signed rule's applied evidence must contain the rule's shape.
|
|
140
157
|
const sig = signatures[row.rule];
|
|
141
158
|
if (sig) {
|
|
159
|
+
if (unsafeSignature(sig)) {
|
|
160
|
+
out.push({ rule: row.rule, problem: `unsafe signature regex (nested quantifiers risk catastrophic backtracking): /${sig}/ — simplify it in runward/rules/${row.rule}.md` });
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
142
163
|
let re;
|
|
143
164
|
try {
|
|
144
165
|
re = new RegExp(sig, "i");
|
package/dist/lib/styles.js
CHANGED
|
@@ -54,6 +54,19 @@ export function section(title) {
|
|
|
54
54
|
export function isNonInteractive() {
|
|
55
55
|
return process.env.RUNWARD_YES === "1" || !process.stdout.isTTY;
|
|
56
56
|
}
|
|
57
|
+
/** Today (UTC, YYYY-MM-DD), or the RUNWARD_NOW override in non-interactive runs. The override is
|
|
58
|
+
* VALIDATED as a real YYYY-MM-DD date: an unvalidated value flowed straight into the OSCAL and the
|
|
59
|
+
* seal, so a malformed env var produced schema-invalid output. A bad value falls back to today. */
|
|
60
|
+
export function generationDate() {
|
|
61
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
62
|
+
const override = process.env.RUNWARD_NOW;
|
|
63
|
+
if (!isNonInteractive() || !override)
|
|
64
|
+
return today;
|
|
65
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(override))
|
|
66
|
+
return today;
|
|
67
|
+
const d = new Date(override + "T00:00:00Z");
|
|
68
|
+
return !isNaN(d.getTime()) && d.toISOString().slice(0, 10) === override ? override : today;
|
|
69
|
+
}
|
|
57
70
|
function stripAnsi(s) {
|
|
58
71
|
// eslint-disable-next-line no-control-regex
|
|
59
72
|
return s.replace(/\[[0-9;]*m/g, "");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "runward",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "After the spec: ship and run. A delivery framework for agentic systems \u2014 floor first, evolution on evidence, governance from day zero, handover with proof.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentic",
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
"regime": "eu-ai-act",
|
|
4
4
|
"label": "EU AI Act (Annex IV)",
|
|
5
5
|
"version": "2024-1689",
|
|
6
|
-
"notes": "Mapped
|
|
6
|
+
"notes": "Mapped against Regulation (EU) 2024/1689 (OJ L, 12 July 2024), as amended by the Digital Omnibus on AI (Council final green light 29 June 2026), which postponed the Annex III high-risk obligations to 2 December 2027 (Annex I to 2 August 2028). The 2 August 2026 milestone remains real for other obligations (Article 50 transparency for systems placed on the market after that date, governance, GPAI enforcement) but is NOT the high-risk / Annex IV deadline. Annex IV wording moves — confirm against the Official Journal before filing.",
|
|
7
7
|
"highRisk": {
|
|
8
|
-
"bindFrom": "2
|
|
9
|
-
"scope": "Annex III"
|
|
8
|
+
"bindFrom": "2 December 2027",
|
|
9
|
+
"scope": "Annex III, as postponed by the Digital Omnibus on AI (June 2026)"
|
|
10
10
|
},
|
|
11
11
|
"articles": {
|
|
12
12
|
"runtimeLogging": "art. 12"
|
|
@@ -53,6 +53,10 @@ This is **one example** of a per-harness turn-end hook, not a privileged one —
|
|
|
53
53
|
|
|
54
54
|
Copy the file into `.kiro/hooks/` in your workspace. The `Stop` trigger runs `runward check --strict` when the agent finishes, and surfaces the verdict in the session — the same one line as the Claude Code hook, another non-privileged example of a per-harness turn-end seam. Kiro also reads `AGENTS.md` natively, and `runward init --tools kiro` mirrors the phase skills as steering files (`.kiro/steering/`, relevance-loaded) — the traced decisions inform the session; the gate stays the only authority.
|
|
55
55
|
|
|
56
|
+
## `bmad-review-layer.toml` — the gate as a review layer BMAD calls
|
|
57
|
+
|
|
58
|
+
Copy the `[[workflow.review_layers]]` block into the customize.toml of BMAD's `bmad-code-review` skill. It adds runward's deterministic gate as one review layer beside BMAD's adversarial LLM reviewers (their extension point explicitly allows "an external reviewer via bash"): they judge the code's craft, runward verifies the traced decisions and their evidence. A complement running on BMAD's flow, not a competitor to it — same inert posture, same exit-code port (ADR-0027).
|
|
59
|
+
|
|
56
60
|
---
|
|
57
61
|
|
|
58
62
|
Adapters are runward-owned templates: `runward update` refreshes them, `runward doctor` verifies them. They are never mission state. Add a new harness by dropping a new sample here — the port contract above does not change.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# runward gate — BMAD review-layer adapter (sample).
|
|
2
|
+
# Inert until you install it: see runward/adapters/README.md.
|
|
3
|
+
#
|
|
4
|
+
# BMAD's bmad-code-review skill exposes a review-layers extension point whose
|
|
5
|
+
# `instruction` may "run anything (e.g. an external reviewer via bash)". This
|
|
6
|
+
# adds runward's deterministic, zero-LLM gate as one review layer BESIDE BMAD's
|
|
7
|
+
# LLM reviewers — a complement, not a replacement: BMAD's subagents judge the
|
|
8
|
+
# code's craft; runward verifies, deterministically, that every CRITICAL/HIGH
|
|
9
|
+
# rule mapped to the phase is accounted for with real, resolving evidence.
|
|
10
|
+
#
|
|
11
|
+
# To install: copy this [[workflow.review_layers]] block into the customize.toml
|
|
12
|
+
# of BMAD's bmad-code-review skill (src/bmm-skills/4-implementation/bmad-code-review/).
|
|
13
|
+
# runward writes nothing into BMAD and runs nothing on its own — you wire it (ADR-0012).
|
|
14
|
+
#
|
|
15
|
+
# Exit codes (the port contract): 0 clean · 1 gaps · 2 no mission found.
|
|
16
|
+
|
|
17
|
+
[[workflow.review_layers]]
|
|
18
|
+
id = "runward-gate"
|
|
19
|
+
name = "runward deterministic gate"
|
|
20
|
+
# `when` (optional) could gate this layer on a runward/ mission being present.
|
|
21
|
+
instruction = """
|
|
22
|
+
Run the runward deterministic conformance gate as an external reviewer, via bash:
|
|
23
|
+
|
|
24
|
+
npx --yes runward check --strict
|
|
25
|
+
|
|
26
|
+
Report its exit code and output verbatim as this layer's finding:
|
|
27
|
+
- exit 0 → the conformance manifests are complete and their evidence resolves (a PASS finding, no severity).
|
|
28
|
+
- exit 1 → report each printed gap as a finding the operator must close before merge (a traced rule is unaccounted for, or an evidence pointer is hollow/stale). Do NOT paraphrase or soften: the gate is deterministic and its verdict is not a matter of judgment.
|
|
29
|
+
- exit 2 → no runward/ mission here; skip this layer (it does not apply to this repo).
|
|
30
|
+
|
|
31
|
+
This layer verifies traced decisions and their evidence; it never judges code quality — that stays the other review layers' job and the operator's at the gate.
|
|
32
|
+
"""
|