@osfactory/har 1.1.0 → 1.3.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.
@@ -0,0 +1,108 @@
1
+ # Run a factory line
2
+
3
+ Execute **one station** of a declared multi-station program:
4
+
5
+ sync → wave plan → parallel HAR slots → cumulative gate → human handoff.
6
+
7
+ The program lives in an **installed line bundle**, not in this workflow. Find
8
+ it with `har line status` — installed lines live at `.har/lines/<id>/line.json`
9
+ and are recorded in `.har/lines.json`. This workflow is the orchestrator;
10
+ station-specific how-to lives in other skills the program lists.
11
+
12
+ HAR already has the primitives — work units, isolated slots, stages, plugins,
13
+ validation records. A line is composition plus a manifest. Do not invent a
14
+ second runner, a stack-specific MCP tool, or GitHub-shaped stations.
15
+
16
+ This workflow is the same on Claude Code (`/factory-line`), Cursor
17
+ (`.cursor/commands/factory-line`), and Codex (`har-factory-line`).
18
+
19
+ ## Inputs
20
+
21
+ Resolve these before doing anything (ask only if not inferable):
22
+
23
+ 1. **Line** — run `har line status` (or MCP `har_line_status`). One installed
24
+ line: use it. Several: ask which. **None installed**: stop and offer
25
+ `har line create <id>` or `har line add <spec>` — do not invent GitHub
26
+ issue numbers, a fixture repo, or a program of your own.
27
+ 2. **Station id** — default: the first station whose bound work is not all
28
+ done / whose gate has not passed.
29
+ 3. **Slot budget** — `har env status` first. One slot per concurrent agent.
30
+ Occupied slots always block.
31
+
32
+ ## Contract (minimum)
33
+
34
+ A line program is JSON with `contractVersion: 1`. Required:
35
+
36
+ - `id`, `stations[]` (ordered; each has `id` + `title`)
37
+ - `gate.cumulative` must be `true`
38
+ - `handoff.autonomousShip` must be `false`
39
+
40
+ Optional: `skills[]` and `mcp[]` (declarations + install hints — HAR does not
41
+ vendor third-party skill packs or add core MCP tools), `plugins[]`,
42
+ `gate.stages[]` with `fromStation` (that stage stays required from this
43
+ station **on**), `extraStages[]`, `traveler`, `stations[].work` / `waves`.
44
+
45
+ `work.source` is `github`, `linear`, `none`, or a free string — the tracker
46
+ is data on the station, not the type of the station. If `waves` is omitted,
47
+ the station is one sequential cell.
48
+
49
+ A QA station is never removed. Adding a station must not drop earlier
50
+ `gate.stages`. When a review finds a new class of defect, add a stage or
51
+ doctor question and tag `fromStation` — that is how the ratchet grows.
52
+
53
+ ## What this workflow must not bake in
54
+
55
+ Stacked PRs, a long-lived integration branch, a named fixture repo, `M0..M5`,
56
+ and "one issue = one PR = one slot" are **instance tactics**. If the template
57
+ does not declare them, do not do them.
58
+
59
+ ## 0. Load and preflight
60
+
61
+ 1. `har line status [id]` — read the program it names. Confirm
62
+ `gate.cumulative` and `handoff.autonomousShip`. `har line status` already
63
+ reports which stations are green and which is next.
64
+ 2. Skills and MCP: confirm required ones are present. Missing optional:
65
+ skip those steps and say so. Do not install third-party packs yourself.
66
+ 3. Print: line id, station id, waves, slot plan, and every gate stage whose
67
+ `fromStation` is this station or an earlier one.
68
+
69
+ ## 1. Wave plan
70
+
71
+ Print `stations[].waves` (or a single cell). One HAR slot per group in a
72
+ wave. Never share a slot between two concurrent groups.
73
+
74
+ ## 2. Execute waves
75
+
76
+ For each wave, one subagent per group, in parallel. Each:
77
+
78
+ 1. Checks the slot is free. Occupied → complete/teardown that slot, or pick
79
+ a free slot in budget.
80
+ 2. `har env launch <slot>` with `--work-id` / `--work-source` / `--work-url`
81
+ / `--work-title` when work is bound. Skip tracker flags when source is
82
+ `none`.
83
+ 3. Edits **only** in the returned work dir.
84
+ 4. Full verify in-slot until green. If this station made a new gate question
85
+ askable, add it — never remove an earlier station's stages.
86
+ 5. Reports back. Subagents never push, PR, complete, or teardown on their own.
87
+
88
+ ## 3. Gate
89
+
90
+ ```bash
91
+ har line gate <station> --line <id> --agent <slot>
92
+ ```
93
+
94
+ That runs every gate stage whose `fromStation` ≤ this station — the cumulative
95
+ set, from data. Add `--force` when the program sets `gate.optInEnv` and you
96
+ want the opt-in jig to run anyway.
97
+
98
+ Line gate stages are **not** part of `har env verify --full`, by design. Run
99
+ both: full verify for the harness contract, `har line gate` for the station.
100
+
101
+ Red gate → fix, re-run. Never hand off a red gate. Never bypass
102
+ (`HAR_SKIP_GATE`, `--no-verify`).
103
+
104
+ ## 4. Handoff
105
+
106
+ Update the `traveler` the template names. Then **stop and wait**. Do not
107
+ merge, push to the default branch, complete a slot, or release. Present a
108
+ session handoff (summary, session branch, preview URLs, gate evidence).
@@ -13,8 +13,12 @@
13
13
  "allowed-tools": "Bash(har *) Bash(npm *) Bash(git *) Bash(docker *) Read Grep Glob Edit Write"
14
14
  }
15
15
  },
16
- "cursor": { "command": true },
17
- "codex": { "promptName": "har-setup" }
16
+ "cursor": {
17
+ "command": true
18
+ },
19
+ "codex": {
20
+ "promptName": "har-setup"
21
+ }
18
22
  },
19
23
  {
20
24
  "id": "har-wt",
@@ -26,8 +30,12 @@
26
30
  "description": "Use BEFORE making any code change in this repository. Launches a har harness slot, which creates an isolated git session worktree — all edits must happen there, never in the main checkout — and verifies the work through the harness when done."
27
31
  }
28
32
  },
29
- "cursor": { "command": true },
30
- "codex": { "promptName": "har-wt" }
33
+ "cursor": {
34
+ "command": true
35
+ },
36
+ "codex": {
37
+ "promptName": "har-wt"
38
+ }
31
39
  },
32
40
  {
33
41
  "id": "har-maintain",
@@ -41,8 +49,29 @@
41
49
  "allowed-tools": "Bash(har *) Bash(git *) Read Grep Glob Edit Write"
42
50
  }
43
51
  },
44
- "cursor": { "command": true },
45
- "codex": { "promptName": "har-maintain" }
52
+ "cursor": {
53
+ "command": true
54
+ },
55
+ "codex": {
56
+ "promptName": "har-maintain"
57
+ }
58
+ },
59
+ {
60
+ "id": "factory-line",
61
+ "body": "factory-line.md",
62
+ "title": "Run one station of a factory line",
63
+ "claude": {
64
+ "frontmatter": {
65
+ "name": "factory-line",
66
+ "description": "Execute one station of an installed factory line (har line status / .har/lines/<id>/line.json): parallel HAR slots, cumulative gate via har line gate, human handoff. Use when asked to run a factory line, the next station, or a milestone program."
67
+ }
68
+ },
69
+ "cursor": {
70
+ "command": true
71
+ },
72
+ "codex": {
73
+ "promptName": "har-factory-line"
74
+ }
46
75
  }
47
76
  ]
48
77
  }
@@ -0,0 +1,24 @@
1
+ name: Check line bundle
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ check-manifest:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-node@v4
14
+ with:
15
+ node-version: 20
16
+ - name: Validate line manifest and program
17
+ run: node scripts/check-manifest.mjs
18
+ - name: Stage scripts are executable
19
+ run: |
20
+ set -euo pipefail
21
+ for script in stages/*.sh; do
22
+ [ -x "$script" ] || { echo "not executable: $script" >&2; exit 1; }
23
+ bash -n "$script"
24
+ done
@@ -0,0 +1,48 @@
1
+ # Authoring guide for coding agents
2
+
3
+ This repository is a **HAR factory line bundle**. Read this before changing it.
4
+
5
+ ## Is this a line or a verification plugin?
6
+
7
+ Ask one question: **should this run on every `har env verify --full`?**
8
+
9
+ | Answer | Bundle kind | Template | Install |
10
+ |---|---|---|---|
11
+ | Yes — it must gate every change | Verification plugin | [har-plugin](https://github.com/os-factory/har-plugin) | `har env add-plugin <spec>` |
12
+ | No — it gates a *station* of a program | Factory line | this repo | `har line add <spec>` |
13
+
14
+ Typical lines: a migration executed in waves, an onboarding program, a
15
+ heavy proof jig (Docker lab, fixture end-to-end, device farm) that must exist
16
+ and be runnable but must not be on the hot path of every verify.
17
+
18
+ If you find yourself wanting to add a line stage to `verificationStages`, stop:
19
+ you wanted a plugin.
20
+
21
+ ## Hard rules
22
+
23
+ - `line.manifest.json` declares `"kind": "line"` and **must not** declare
24
+ `verificationStages`. The schema rejects it.
25
+ - `gate.cumulative` is `true`. A stage tagged `fromStation: X` is required at X
26
+ **and every later station**. Adding a station may never drop an earlier
27
+ station's stages.
28
+ - `handoff.autonomousShip` is `false`. Agents hand off; humans merge and
29
+ release.
30
+ - `skills[]` and `mcp[]` are **declarations with install hints**. HAR does not
31
+ vendor skill packs or MCP servers, and a line never introduces a core tool
32
+ named after a tracker or a framework.
33
+ - Station names, tracker ids, branch strategy, and fixture repos are *instance
34
+ data*. They belong in `line.json`, never in the tooling.
35
+
36
+ ## Validate
37
+
38
+ ```bash
39
+ node scripts/check-manifest.mjs
40
+ ```
41
+
42
+ Then prove the invariant on a scratch harness:
43
+
44
+ ```bash
45
+ har line add ./ # from a repo with a .har/ harness
46
+ har line status <id>
47
+ git diff -- .har/stages.json # verificationStages must be untouched
48
+ ```
@@ -0,0 +1,72 @@
1
+ # har-line
2
+
3
+ A GitHub template for authoring **HAR factory lines**.
4
+
5
+ A *factory line* is a program: an ordered set of **stations** plus a
6
+ **cumulative gate**. It composes what HAR 1.0 already has — work units, slots,
7
+ stages, plugins, skills, MCP. It is not a fourth marketplace and not a second
8
+ stage runner.
9
+
10
+ > **A line is not a verification plugin.** Installing a line registers its
11
+ > stages but **never** adds them to `verificationStages`. Default
12
+ > `har env verify --full` takes exactly as long as it did before. That is the
13
+ > entire reason the two bundle kinds are separate.
14
+ >
15
+ > If your check should gate *every* verify, use
16
+ > [os-factory/har-plugin](https://github.com/os-factory/har-plugin) instead.
17
+
18
+ ## Quick start
19
+
20
+ 1. **Use this template** → name your repo (e.g. `onboarding-line`).
21
+ 2. Rename the example: `line.manifest.json` `id`, `line.json` `id`/`title`, and
22
+ the stage script under `stages/`.
23
+ 3. Describe your stations in `line.json`.
24
+ 4. Install it into a product repo:
25
+
26
+ ```bash
27
+ har line add github:your-org/onboarding-line
28
+ har line status onboarding-line
29
+ har line gate S2 --line onboarding-line
30
+ ```
31
+
32
+ ## Package layout
33
+
34
+ ```text
35
+ har-line/
36
+ ├── line.manifest.json # kind: line — what `har line add` applies
37
+ ├── line.json # the program: stations, gate, handoff
38
+ ├── stages/
39
+ │ └── example-gate.sh # extra stage — registered, NOT on verify
40
+ ├── docs/AUTHORING.md # how to write a line
41
+ ├── scripts/check-manifest.mjs
42
+ └── AGENTS.md # line vs verification plugin, for coding agents
43
+ ```
44
+
45
+ ## Install channels
46
+
47
+ | Spec | Example |
48
+ |---|---|
49
+ | Local path | `har line add ./har-line` |
50
+ | Git | `har line add github:acme/onboarding-line` |
51
+ | npm | `har line add @acme/onboarding-line` |
52
+
53
+ Add a `package.json` (name, version, `files`) only if you want the npm channel —
54
+ git and path installs need nothing else.
55
+
56
+ Resolution order is path → git → bundled id → npm, the same resolver plugins
57
+ use. The package root must contain `line.manifest.json`.
58
+
59
+ `har env add-plugin` **refuses** a line bundle and points at `har line add`.
60
+
61
+ ## The invariant
62
+
63
+ After `har line add`, `.har/stages.json` `verificationStages` is unchanged —
64
+ no new ids, same default verify duration. `scripts/check-manifest.mjs` asserts
65
+ the manifest side of that; the CLI asserts the applied side and refuses to
66
+ write if the verify plan moved.
67
+
68
+ ## Related
69
+
70
+ - [os-factory/har](https://github.com/os-factory/har) — the CLI and MCP control plane
71
+ - [os-factory/har-plugin](https://github.com/os-factory/har-plugin) — template for **verification plugins** (these *do* join verify)
72
+ - [Factory lines guide](https://har.osfactory.dev/docs/guides/factory-lines/)
@@ -0,0 +1,79 @@
1
+ # Authoring a factory line
2
+
3
+ ## 1. The program (`line.json`)
4
+
5
+ `contractVersion: 1`. Additive fields are fine; renaming a field is a new
6
+ version.
7
+
8
+ | Field | Required | Meaning |
9
+ |---|---|---|
10
+ | `id` | yes | Stable kebab id. Must match `line.manifest.json`. |
11
+ | `title` | yes | Human title. |
12
+ | `stations[]` | yes | Ordered. Array order *is* station order. |
13
+ | `stations[].id` | yes | Stable, shell-friendly. |
14
+ | `stations[].work` | no | Tracker binding: `source` (`github`, `linear`, `none`, …), `ids[]` passed to `har env launch --work-id`. |
15
+ | `stations[].waves` | no | Each inner array is one wave; cells in a wave run in parallel slots. |
16
+ | `skills[]` | no | Skills the line expects. `role` is `orchestrator` or `station`; `install` is a hint (`repo:…`, `upstream:url`, `agent-managed`). |
17
+ | `mcp[]` | no | MCP **servers** the line expects. `required: false` means skip those steps when absent. |
18
+ | `plugins[]` | no | Verification plugin ids the gate assumes (`har env add-plugin`). |
19
+ | `gate.cumulative` | yes | Must be `true`. |
20
+ | `gate.optInEnv` | no | When set (e.g. `HAR_FIXTURE_E2E`), the gate is skipped unless that env is `1`. |
21
+ | `gate.stages[].fromStation` | yes | The ratchet tag: required from this station onward. |
22
+ | `extraStages[]` | no | Stages this line adds beyond the profile defaults. |
23
+ | `handoff.autonomousShip` | yes | Must be `false`. |
24
+ | `traveler` | no | Where durable notes live given the repo's merge policy. |
25
+ | `prototypeNotes[]` | no | Instance tactics that must not leak into the primitive. |
26
+
27
+ ## 2. The manifest (`line.manifest.json`)
28
+
29
+ ```json
30
+ {
31
+ "kind": "line",
32
+ "id": "your-line",
33
+ "program": "line.json",
34
+ "files": [
35
+ { "src": "stages/your-gate.sh", "dest": ".har/stages/your-gate.sh", "executable": true }
36
+ ],
37
+ "stages": [{ "id": "your-gate", "kind": "test", "script": "stages/your-gate.sh", "tier": "full" }]
38
+ }
39
+ ```
40
+
41
+ - `files[]` are copied into the target repo verbatim.
42
+ - `stages[]` are **registered** in `.har/stages.json` — and nothing else. They
43
+ are not appended to `verificationStages`, and the CLI refuses to write if
44
+ applying the bundle would have moved that list.
45
+ - Declaring `verificationStages` here is a schema error, not a warning.
46
+
47
+ ## 3. The gate stage
48
+
49
+ A line stage is an ordinary HAR stage script: stdout is one JSON result object,
50
+ stderr is progress, `$1` is the agent slot id, artifacts go under
51
+ `.har/artifacts/<stage-id>/`. See `.har/STAGES.md` in any harness for the full
52
+ contract.
53
+
54
+ Run it through the line, not through verify:
55
+
56
+ ```bash
57
+ har line gate S2 --line your-line
58
+ ```
59
+
60
+ ## 4. Growing the ratchet
61
+
62
+ To ask a question the previous gate forgot:
63
+
64
+ 1. Add a stage (or a `doctor` check) that asks it.
65
+ 2. Tag it `fromStation` of the station that made the question askable.
66
+ 3. Leave every earlier tag in place.
67
+
68
+ That is how a review comment becomes a permanent question instead of a habit.
69
+
70
+ ## 5. Publish
71
+
72
+ ```bash
73
+ git init && git commit -am "feat: my line"
74
+ gh repo create your-org/your-line --public --source . --push
75
+ ```
76
+
77
+ Consumers install with `har line add github:your-org/your-line`. For npm, keep
78
+ `package.json` and `npm publish` — `har line add @your-org/your-line` packs it
79
+ with `npm pack` and reads `line.manifest.json` from the package root.
@@ -0,0 +1,72 @@
1
+ {
2
+ "contractVersion": 1,
3
+ "id": "example-line",
4
+ "title": "Example factory line",
5
+ "description": "A three-station program showing the shape a factory line takes: stations that produce something, a cumulative gate that grows as the line grows, and a handoff that a human owns. Rename it and make the stations describe your own work.",
6
+ "skills": [
7
+ {
8
+ "id": "factory-line",
9
+ "role": "orchestrator",
10
+ "install": "repo:.claude/skills/factory-line"
11
+ }
12
+ ],
13
+ "mcp": [
14
+ {
15
+ "name": "github",
16
+ "why": "issue tracker and pull requests",
17
+ "required": false
18
+ }
19
+ ],
20
+ "plugins": [],
21
+ "stations": [
22
+ {
23
+ "id": "S1",
24
+ "title": "Draft",
25
+ "description": "Agree the contract before anyone writes code: schema, interfaces, the invariant that must hold.",
26
+ "work": { "source": "none", "ids": [], "optional": true }
27
+ },
28
+ {
29
+ "id": "S2",
30
+ "title": "Build",
31
+ "description": "Implement the contract in isolated slots, one work unit per slot.",
32
+ "work": { "source": "none", "ids": [], "optional": true }
33
+ },
34
+ {
35
+ "id": "S3",
36
+ "title": "Prove",
37
+ "description": "Demonstrate the invariant on a real repository, then hand off for human review.",
38
+ "work": { "source": "none", "ids": [], "optional": true }
39
+ }
40
+ ],
41
+ "gate": {
42
+ "cumulative": true,
43
+ "optInEnv": null,
44
+ "stages": [
45
+ {
46
+ "id": "example-gate",
47
+ "fromStation": "S2",
48
+ "tier": "full"
49
+ }
50
+ ]
51
+ },
52
+ "extraStages": [
53
+ {
54
+ "id": "example-gate",
55
+ "kind": "test",
56
+ "tier": "full",
57
+ "description": "Line-owned gate — registered in .har/stages.json, absent from verificationStages"
58
+ }
59
+ ],
60
+ "handoff": {
61
+ "autonomousShip": false,
62
+ "waitFor": "human review before merge or release"
63
+ },
64
+ "traveler": {
65
+ "kind": "github-issue-comment",
66
+ "note": "Durable notes live on the tracker issue — a squash merge drops commit-body footers."
67
+ },
68
+ "prototypeNotes": [
69
+ "Station names (Draft/Build/Prove) are instance data — rename them for your program.",
70
+ "This example binds no tracker work; set stations[].work.source and .ids to bind GitHub or Linear."
71
+ ]
72
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "kind": "line",
3
+ "id": "example-line",
4
+ "title": "Example factory line",
5
+ "program": "line.json",
6
+ "files": [
7
+ {
8
+ "src": "stages/example-gate.sh",
9
+ "dest": ".har/stages/example-gate.sh",
10
+ "executable": true
11
+ }
12
+ ],
13
+ "stages": [
14
+ {
15
+ "id": "example-gate",
16
+ "kind": "test",
17
+ "description": "Line-owned gate for example-line — registered, never on verify",
18
+ "script": "stages/example-gate.sh",
19
+ "requiresAgentId": true,
20
+ "artifacts": [
21
+ {
22
+ "path": ".har/artifacts/example-gate",
23
+ "kind": "directory",
24
+ "description": "Artifacts for the example-gate stage"
25
+ }
26
+ ],
27
+ "tier": "full"
28
+ }
29
+ ],
30
+ "nextSteps": [
31
+ "Edit .har/lines/example-line/line.json — rename the line and describe your stations",
32
+ "Edit .har/stages/example-gate.sh — replace the TODO with the real gate",
33
+ "har line status example-line",
34
+ "har line gate S2 --line example-line"
35
+ ],
36
+ "docsPath": ".har/lines/example-line/README.md"
37
+ }
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Validate this line bundle without installing it.
4
+ *
5
+ * Checks the invariants that make a line a line — most importantly that the
6
+ * manifest declares no `verificationStages`, so applying it can never widen
7
+ * `har env verify --full`.
8
+ *
9
+ * Usage: node scripts/check-manifest.mjs [bundle-dir]
10
+ */
11
+ import { readFileSync, existsSync } from 'node:fs';
12
+ import { join, resolve, dirname } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+
15
+ const bundleDir = resolve(process.argv[2] ?? join(dirname(fileURLToPath(import.meta.url)), '..'));
16
+ const errors = [];
17
+ const warnings = [];
18
+
19
+ function readJson(relPath) {
20
+ const abs = join(bundleDir, relPath);
21
+ if (!existsSync(abs)) {
22
+ errors.push(`missing file: ${relPath}`);
23
+ return null;
24
+ }
25
+ try {
26
+ return JSON.parse(readFileSync(abs, 'utf8'));
27
+ } catch (err) {
28
+ errors.push(`invalid JSON in ${relPath}: ${err.message}`);
29
+ return null;
30
+ }
31
+ }
32
+
33
+ const manifest = readJson('line.manifest.json');
34
+
35
+ if (manifest) {
36
+ if (manifest.kind !== 'line') {
37
+ errors.push('line.manifest.json must declare "kind": "line" (a plugin manifest is not a line)');
38
+ }
39
+ if (manifest.verificationStages !== undefined) {
40
+ errors.push(
41
+ 'line.manifest.json must NOT declare verificationStages — line gate stages are opt-in and run via `har line gate`',
42
+ );
43
+ }
44
+ if (typeof manifest.id !== 'string' || !/^[a-z0-9][a-z0-9._-]*$/.test(manifest.id)) {
45
+ errors.push('line.manifest.json id must be a lowercase slug');
46
+ }
47
+ for (const file of manifest.files ?? []) {
48
+ if (!existsSync(join(bundleDir, file.src))) {
49
+ errors.push(`files[].src not found: ${file.src}`);
50
+ }
51
+ }
52
+ }
53
+
54
+ const program = manifest ? readJson(manifest.program ?? 'line.json') : null;
55
+
56
+ if (manifest && program) {
57
+ if (program.id !== manifest.id) {
58
+ errors.push(`program id "${program.id}" does not match manifest id "${manifest.id}"`);
59
+ }
60
+ if (program.contractVersion !== 1) {
61
+ errors.push('program contractVersion must be 1');
62
+ }
63
+ if (program.gate?.cumulative !== true) {
64
+ errors.push('gate.cumulative must be true — a line never drops an earlier station\'s stages');
65
+ }
66
+ if (program.handoff?.autonomousShip !== false) {
67
+ errors.push('handoff.autonomousShip must be false — agents hand off, they do not ship');
68
+ }
69
+
70
+ const stationIds = (program.stations ?? []).map((s) => s.id);
71
+ if (stationIds.length === 0) {
72
+ errors.push('program needs at least one station');
73
+ }
74
+ if (new Set(stationIds).size !== stationIds.length) {
75
+ errors.push('station ids must be unique');
76
+ }
77
+
78
+ const registeredIds = new Set((manifest.stages ?? []).map((s) => s.id));
79
+ for (const stage of program.gate?.stages ?? []) {
80
+ if (!stationIds.includes(stage.fromStation)) {
81
+ errors.push(`gate stage "${stage.id}" tags unknown station "${stage.fromStation}"`);
82
+ }
83
+ if (!registeredIds.has(stage.id)) {
84
+ warnings.push(
85
+ `gate stage "${stage.id}" is not registered by this bundle — the target repo must already provide it (e.g. via a verification plugin)`,
86
+ );
87
+ }
88
+ }
89
+ }
90
+
91
+ for (const warning of warnings) {
92
+ console.warn(`warn: ${warning}`);
93
+ }
94
+
95
+ if (errors.length > 0) {
96
+ for (const err of errors) {
97
+ console.error(`error: ${err}`);
98
+ }
99
+ console.error(`\n${errors.length} error(s) in ${bundleDir}`);
100
+ process.exit(1);
101
+ }
102
+
103
+ console.log(`ok: ${bundleDir} is a valid HAR line bundle`);
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env bash
2
+ # Line-owned gate for example-line (never on verify)
3
+ # Custom HAR stage: example-gate (kind: test)
4
+ #
5
+ # Stage script contract (full reference: .har/STAGES.md):
6
+ # - stdout: a single JSON result object (status, stageId, agent_id, ...)
7
+ # - stderr: human-readable progress
8
+ # - $1: agent slot id; extra args may follow
9
+ # - artifacts: write reports/screenshots/logs under .har/artifacts/example-gate/
10
+ # - exit code: the real status (0 = pass)
11
+ #
12
+ # Usage: ./.har/stages/example-gate.sh <agent-id> [extra args...]
13
+ set -euo pipefail
14
+
15
+ # 1.0 stage surface: the runner exports WORK_DIR, ENV_FILE, AGENT_ID and
16
+ # HAR_HARNESS_DIR, with harness.env and the slot env file already sourced —
17
+ # agent-slot.sh is retired (1.0 migration).
18
+ HARNESS_DIR="${HAR_HARNESS_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
19
+ REPO_ROOT="$(cd "$HARNESS_DIR/.." && pwd)"
20
+
21
+ AGENT_ID="${1:-${AGENT_ID:?Usage: example-gate.sh <agent-id> [extra args...]}}"
22
+ now_ms() { node -e 'process.stdout.write(String(Date.now()))' 2>/dev/null || echo 0; }
23
+
24
+ # JSON-escape step output; truncate to 50 lines in node (avoids SIGPIPE under pipefail).
25
+ escape_step_output() {
26
+ printf '%s' "$1" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const s=d.trim().split('\n').slice(0,50).join('\n');process.stdout.write(JSON.stringify(s))})" 2>/dev/null || echo '""'
27
+ }
28
+
29
+ ENV_FILE="${ENV_FILE:?No slot env for agent ${AGENT_ID} — run har env launch ${AGENT_ID} first}"
30
+ WORK_DIR="${WORK_DIR:?No slot work dir for agent ${AGENT_ID} — run har env launch ${AGENT_ID} first}"
31
+ ARTIFACTS_DIR="$HARNESS_DIR/artifacts/example-gate"
32
+ mkdir -p "$ARTIFACTS_DIR"
33
+
34
+ echo "==> [example-gate agent-${AGENT_ID}] running in ${WORK_DIR}" >&2
35
+ START=$(now_ms)
36
+
37
+ # ── TODO: implement the stage ────────────────────────────────────────────────
38
+ # Run your checks from $WORK_DIR (the agent's isolated checkout); the slot's
39
+ # ports and env are loaded from $ENV_FILE. Save artifacts to $ARTIFACTS_DIR.
40
+ set +e
41
+ OUTPUT=$(cd "$WORK_DIR" && echo "TODO: implement .har/stages/example-gate.sh" && false)
42
+ EXIT_CODE=$?
43
+ set -e
44
+
45
+ END=$(now_ms)
46
+ STATUS="fail"
47
+ [ "$EXIT_CODE" = "0" ] && STATUS="pass"
48
+ OUTPUT_JSON=$(escape_step_output "$OUTPUT")
49
+
50
+ node -e "process.stdout.write(JSON.stringify({
51
+ status: '$STATUS',
52
+ stageId: 'example-gate',
53
+ kind: 'test',
54
+ agent_id: $AGENT_ID,
55
+ total_ms: $(( END - START )),
56
+ output: $OUTPUT_JSON
57
+ }, null, 2) + '\n');"
58
+
59
+ exit "$EXIT_CODE"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@osfactory/har",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "HAR: open agent harness CLI and MCP server for coding agents, isolated worktrees, and software-factory workflows",
5
5
  "keywords": [
6
6
  "har",