@dombaras/agent-harness 0.1.10 → 0.1.12

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
@@ -11,8 +11,9 @@ and QA gates — then deploys them into any project.
11
11
 
12
12
  ## Why
13
13
 
14
- - The agent harness is tooling, not application code. It should not be committed
15
- into every app repo.
14
+ - The agent harness is tooling, not application code. Its source lives in the
15
+ npm package you deploy it with `init`/`update` rather than hand-copying it
16
+ into every repo.
16
17
  - One canonical, versioned source of truth for personas/rules/QA tiers, deployed
17
18
  on demand and updatable via `npx @dombaras/agent-harness update`.
18
19
 
@@ -44,6 +45,7 @@ npx @dombaras/agent-harness init --target . --dry-run
44
45
  | `scripts/qa/*` | `test:dispatch` / `test:governance` / `test:qa-plan` gates + QA-script wiring check | harness (overwrite) |
45
46
  | `.agents/memory/*` | project data (domain-map, stack-versions, handoff, locations, model-routing, history, flow-map, qa-plan) | **project** (create-if-missing) |
46
47
  | `.harness.json` | deployed version + project profile + per-file checksums | harness |
48
+ | `.gitignore` | harness-managed section (`.harness-backup/`) merged in, project entries preserved | harness (merged) |
47
49
 
48
50
  ### `opencode.json` is merged, not clobbered
49
51
 
@@ -108,6 +110,10 @@ warns but does not block; the guarantee only applies to registered flows.
108
110
  `deepseek/deepseek-v4-pro` (default, direct API key), `zen`/`opencode` →
109
111
  `opencode/deepseek-v4-pro` (gateway), `pickle` → `opencode/big-pickle`, or any
110
112
  explicit `provider/model`. Restart opencode after switching (config reads once).
113
+ - **`/board` command** — every deploy ships an opencode command
114
+ (`.opencode/command/board.md`) that reads the local `BACKLOG.md` and renders a
115
+ clean task list (Priority · Title · Short description). Read-only, no git — the
116
+ local file is the source of truth.
111
117
 
112
118
  ## QA gates
113
119
 
@@ -144,11 +150,13 @@ npx @dombaras/agent-harness update --target /path/to/project
144
150
  - Overwrites harness-owned files, preserves `.agents/memory/*` and `BACKLOG.md`.
145
151
  - Auto-wires the harness gate scripts (`test:dispatch`, `test:governance`,
146
152
  `test:qa-plan`, `test:backlog`) into the target's `package.json` (merged, add-only).
147
- - **Auto-commits** only the harness files it changed (`chore(harness): @dombaras/agent-harness
153
+ - **Auto-commits** the harness files it changed (`chore(harness): @dombaras/agent-harness
148
154
  <old> -> <new>`) and **pushes** to origin, so the next session never sees unexplained
149
155
  modified harness files. Your unrelated uncommitted work is never staged.
150
156
  - `--no-commit` to skip commit+push, `--no-push` to commit but not push.
151
- - `init` never commits.
157
+ - `init` auto-commits + pushes by default too: it commits everything it scaffolds
158
+ (including `.agents/memory/*`, `.agents/features/*`, and `BACKLOG.md`) so a fresh
159
+ project starts fully tracked and no harness file is left untracked.
152
160
  - Harness-owned paths are **force-added**, so a project that gitignores deployment
153
161
  artifacts (an inherited `.agents/` `.opencode/` `AGENTS.md` pattern) still gets the
154
162
  harness commit instead of a raw `git add` failure that silently skips it — force-added
@@ -166,6 +174,10 @@ npx @dombaras/agent-harness update --target /path/to/project
166
174
  last deploy (tracked by checksum in `.harness.json`), it is backed up to
167
175
  `.harness-backup/<timestamp>/` before overwrite.
168
176
  - `opencode.json` is merged (project keys preserved).
177
+ - **Managed `.gitignore`**: the harness merges a marked section (`.harness-backup/`, its
178
+ local rollback artifacts) into the project's `.gitignore`, preserving every project entry.
179
+ New harness files are tracked (committed), not ignored — so they don't need a gitignore
180
+ change. If the harness ever adds a new local-only artifact, `update` re-merges the entry.
169
181
  - `--dry-run` previews the plan without writing.
170
182
 
171
183
  ### Backlog migration (`migrate-backlog`)
@@ -217,6 +217,25 @@ function mergePackageJson(target) {
217
217
  return { text: JSON.stringify(merged, null, 2) + "\n", hadPkg };
218
218
  }
219
219
 
220
+ // Harness-managed .gitignore entries. These are LOCAL-ONLY artifacts (never
221
+ // committed): everything else the harness deploys is tracked by git. Entries
222
+ // live in a marked section so `update` can re-merge them (and pick up new
223
+ // entries) without touching the project's own ignores.
224
+ const GITIGNORE_MARKER = "# agent-harness (managed below)";
225
+ const HARNESS_IGNORE_ENTRIES = [".harness-backup/"];
226
+
227
+ function mergeGitignore(target) {
228
+ const p = path.join(target, ".gitignore");
229
+ const existing = fs.existsSync(p) ? fs.readFileSync(p, "utf8") : "";
230
+ const lines = existing.split(/\r?\n/);
231
+ const idx = lines.findIndex((l) => l === GITIGNORE_MARKER);
232
+ const kept = idx === -1 ? lines : lines.slice(0, idx);
233
+ while (kept.length && kept[kept.length - 1].trim() === "") kept.pop();
234
+ const managed = [GITIGNORE_MARKER, ...HARNESS_IGNORE_ENTRIES, ""];
235
+ const out = kept.length ? [...kept, "", ...managed] : [...managed];
236
+ return out.join("\n");
237
+ }
238
+
220
239
  // ---------------------------------------------------------------- git helpers
221
240
 
222
241
  function runGit(target, args) {
@@ -348,6 +367,23 @@ async function deploy(target, opts) {
348
367
  }
349
368
  }
350
369
 
370
+ // Merge harness-managed gitignore entries (add-only, marked section).
371
+ {
372
+ const text = mergeGitignore(target);
373
+ const key = ".gitignore";
374
+ const targetAbs = path.join(target, ".gitignore");
375
+ const had = fs.existsSync(targetAbs);
376
+ const current = had ? fs.readFileSync(targetAbs, "utf8") : null;
377
+ if (!had) actions.push({ rel: key, kind: "create" });
378
+ else if (current !== text) actions.push({ rel: key, kind: "merge" });
379
+ else actions.push({ rel: key, kind: "unchanged" });
380
+ manifest[key] = sha256(text);
381
+ if (!dryRun) {
382
+ fs.mkdirSync(path.dirname(targetAbs), { recursive: true });
383
+ fs.writeFileSync(targetAbs, text, "utf8");
384
+ }
385
+ }
386
+
351
387
  for (const { abs, rel } of listFiles(TEMPLATES_DIR)) {
352
388
  const key = relKey(rel);
353
389
  const targetAbs = path.join(target, rel);
@@ -425,15 +461,15 @@ async function deploy(target, opts) {
425
461
  fs.writeFileSync(path.join(target, CONFIG_FILE), JSON.stringify(config, null, 2) + "\n", "utf8");
426
462
  }
427
463
 
428
- // Auto-commit only this run's changed harness-owned files (update only).
464
+ // Auto-commit the files this run created/changed so nothing the harness
465
+ // deploys is left untracked. Runs for both `init` and `update`. Excludes only
466
+ // rollback artifacts; freshly-scaffolded project-data files (memory/features/
467
+ // BACKLOG) are committed so they start tracked, while pre-existing project
468
+ // data is never touched (a "preserve" never enters `changedRelsAll`).
429
469
  let commitResult = null;
430
- if (isUpdate && !dryRun && !nothingToUpdate) {
431
- const changedRels = changedRelsAll.filter(
432
- (r) =>
433
- !r.startsWith(".agents/memory/") &&
434
- !r.startsWith(".agents/features/") &&
435
- !r.startsWith(".harness-backup/")
436
- );
470
+ const shouldCommit = !dryRun && (!isUpdate || !nothingToUpdate);
471
+ if (shouldCommit) {
472
+ const changedRels = changedRelsAll.filter((r) => !r.startsWith(".harness-backup/"));
437
473
  changedRels.push(CONFIG_FILE); // .harness.json reflects the new deployed version
438
474
  commitResult = commitHarnessChanges(target, changedRels, {
439
475
  commit,
@@ -527,13 +563,14 @@ function printUsage() {
527
563
  console.log(
528
564
  `agent-harness v${PKG.version}\n\n` +
529
565
  `Usage:\n` +
530
- ` agent-harness init [--target <dir>] [--name <project>] [--domain <desc>] [--yes] [--dry-run]\n` +
566
+ ` agent-harness init [--target <dir>] [--name <project>] [--domain <desc>] [--yes] [--dry-run] [--no-commit] [--no-push]\n` +
531
567
  ` agent-harness update [--target <dir>] [--dry-run] [--no-commit] [--no-push]\n` +
532
568
  ` agent-harness migrate-backlog [--target <dir>]\n` +
533
569
  `\n` +
534
- ` update auto-commits only the harness files it changes (chore(harness): ...) and\n` +
535
- ` pushes to origin by default. Use --no-commit to skip commit+push, or --no-push\n` +
536
- ` to commit but not push. Your unrelated uncommitted work is never staged.\n` +
570
+ ` init and update auto-commit only the harness files they create/change\n` +
571
+ ` (chore(harness): ...) and push to origin by default. Use --no-commit to\n` +
572
+ ` skip commit+push, or --no-push to commit but not push. Your unrelated\n` +
573
+ ` uncommitted work is never staged.\n` +
537
574
  `\n` +
538
575
  ` migrate-backlog consolidates any live \`.agents/features/INDEX.md\` F-rows and\n` +
539
576
  ` \`.agents/memory/todo.md\` into the canonical BACKLOG.md, then rewrites those\n` +
@@ -785,10 +822,11 @@ async function init(target, flags) {
785
822
  yes: !!flags["--yes"],
786
823
  dryRun: !!flags["--dry-run"],
787
824
  isUpdate: false,
788
- commit: false,
789
- push: false,
825
+ commit: !flags["--no-commit"],
826
+ push: !flags["--no-commit"] && !flags["--no-push"],
790
827
  });
791
828
  printSummary(result, false);
829
+ printCommitResult(result);
792
830
  if (!result.dryRun) printNextSteps();
793
831
  }
794
832
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dombaras/agent-harness",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Reusable multi-agent harness for AI-assisted development: personas, skills, operating rules, model routing, and QA gates. Deploy into any project with `npx @dombaras/agent-harness init`.",
5
5
  "bin": {
6
6
  "agent-harness": "bin/agent-harness.js"
@@ -103,9 +103,11 @@ The canonical rulebook. The always-loaded summary is `.agents/rules/00-operating
103
103
 
104
104
  ## 7. Definition of Done & Continuous Backup
105
105
 
106
- Every completed task passes this gate, in order:
106
+ Every completed task closes with this gate. **QA gates dev work only:** steps 0-2
107
+ run when the session touched code paths — a backlog-only (or docs-only) change skips
108
+ them. Steps 3-5 are the unconditional session close; they are NOT gated by QA.
107
109
 
108
- 0. **Feature Completeness Gate**:
110
+ 0. **Feature Completeness Gate** (code changes only):
109
111
  - [ ] 5 UI states handled on every affected screen (list them).
110
112
  - [ ] No `catch` blocks that only `console.warn` without user-facing feedback.
111
113
  - [ ] Haptic/feedback consistency for every user-initiated action.
@@ -113,10 +115,11 @@ Every completed task passes this gate, in order:
113
115
  - [ ] Cross-screen audit if a new UX pattern was introduced.
114
116
  - [ ] No modified file over ~500 lines without extracting components.
115
117
  - [ ] No blanket file-level `eslint-disable`/`@ts-nocheck`/`@ts-ignore` suppressions (line-level only, each with a reason).
116
- 1. **QA tier** — `qa-architect` inspects the diff, picks the tier BY COVERAGE (not path-label), records the coverage map in `.agents/memory/qa-plan.md`, and authors progression tests for any GAP; if `qa-architect` is not dispatched the orchestrator plans in its place. `qa-runner` executes and makes it pass (§6). A change is not verified until `npm run test:qa-plan` passes: every changed code path covered or waivered, **and** every touched flow (`.agents/memory/flow-map.md`) has every sibling surface and declared variant addressed in the plan's `## Parallel-surface & variant audit`, **AND** the executed tier exercised the modified path.
117
- 2. **Security check** — apply `security-engineer` when the change touches data/auth/input/secrets/deps.
118
- 3. **Commit** — concise `feat:` / `fix:` / `refactor:` message.
119
- 4. **Push** — `git push origin main`.
118
+ 1. **QA tier** (code changes only) — `qa-architect` inspects the diff, picks the tier BY COVERAGE (not path-label), records the coverage map in `.agents/memory/qa-plan.md`, and authors progression tests for any GAP; if `qa-architect` is not dispatched the orchestrator plans in its place. `qa-runner` executes and makes it pass (§6). A change is not verified until `npm run test:qa-plan` passes: every changed code path covered or waivered, **and** every touched flow (`.agents/memory/flow-map.md`) has every sibling surface and declared variant addressed in the plan's `## Parallel-surface & variant audit`, **AND** the executed tier exercised the modified path.
119
+ 2. **Security check** (code changes only) — apply `security-engineer` when the change touches data/auth/input/secrets/deps.
120
+ 3. **Backlog write-back** (always) move every task this session shipped from `Open` to `Archive (shipped · done)` in `BACKLOG.md` (with date/commit), then stage it explicitly: `git add BACKLOG.md` (it starts untracked — `git commit -am` / `git commit` without `add` will NOT pick it up). The board syncs across sessions only through git, so this delta is committed with the code, never left uncommitted.
121
+ 4. **Commit** (always) concise `feat:` / `fix:` / `refactor:` message, including the `BACKLOG.md` delta.
122
+ 5. **Push** (always) — `git push origin main`.
120
123
  - **Mechanical floor**: a pre-push hook should run `npm run test:quick` (project-provided; the harness does not install git hooks).
121
124
  - **Lean permissions**: grant only the narrowest `git`/`gh` action needed; escalate only after a command has actually failed.
122
125
  - Git may not be on PATH in the default shell — use the full path (`.agents/memory/locations.md`).
@@ -23,12 +23,12 @@ Before reading or editing any file for a task, dispatch the relevant personas vi
23
23
  4. **Verify stack versions** before writing framework code (`.agents/memory/stack-versions.md`).
24
24
  5. **Token discipline** — search before read, targeted reads, batch reads, don't re-read unchanged files, right-size QA.
25
25
  6. **Static ≠ runtime** — never declare verified from `tsc` alone; execute a real runtime/API path that exercises the MODIFIED code — a tier label never proves coverage. `test:qa-plan` gates every changed code path to a covering assertion or waiver, and every touched flow's sibling surfaces / variants to an audit entry (`.agents/memory/flow-map.md` — "fix one, fix all").
26
- 7. **Commit** with `feat:`/`fix:`/`refactor:` then push to main.
26
+ 7. **Commit** with `feat:`/`fix:`/`refactor:` then push to main — including any `BACKLOG.md` delta.
27
27
 
28
28
  ## Definition of Done
29
29
 
30
- Right-sized QA tier security lens (if the change touches data/auth/input/secrets) → commit → push.
30
+ QA tier + security lens gate **code changes only** (a backlog-only change skips them). Every session closes with **move shipped `BACKLOG.md` rows to `Archive` → commit → push** — the backlog write-back is never QA-gated.
31
31
 
32
32
  ## Session checklist
33
33
 
34
- Start: read `.agents/memory/locations.md` + `.agents/memory/model-routing.md` + `BACKLOG.md`. End: record the dispatch log and update the handoff memory.
34
+ Start: read `.agents/memory/locations.md` + `.agents/memory/model-routing.md` + `BACKLOG.md`. End: move shipped `BACKLOG.md` rows to `Archive`, record the dispatch log, and update the handoff memory.
@@ -48,3 +48,4 @@ Date: <YYYY-MM-DD>
48
48
  - Update this at session end even if the user doesn't ask, per the DoD in `.agents/AGENTS.md` §8.
49
49
  - Keep it under ~40 lines. Code is the source of truth; the handoff is a pointer, not a spec.
50
50
  - Keeping `.agents/memory/locations.md` current is part of this skill. Read it first, update it last.
51
+ - **You only touch handoff memory.** The board write-back — moving shipped `BACKLOG.md` rows to `Archive (shipped · done)` and committing them — is the orchestrator's DoD §7 step, not yours. If it hasn't been done, call it out in your output; never edit `BACKLOG.md` or run git yourself.
@@ -0,0 +1,21 @@
1
+ ---
2
+ description: Show the local BACKLOG.md as a clean task list (title, short description, priority). Read-only — no git.
3
+ ---
4
+
5
+ Read `BACKLOG.md` at the repo root and display a clean, human-readable task list.
6
+ The local file is the source of truth — do NOT run git, do NOT pull, do NOT edit
7
+ anything.
8
+
9
+ Render the `Open` rows (and the `Active / next` picks if present) as a table with
10
+ exactly these columns:
11
+
12
+ | Priority | Title | Short description |
13
+ |----------|-------|-------------------|
14
+ | P1 | F-001 | block something |
15
+
16
+ - **Priority** = the `P` cell.
17
+ - **Title** = the row's ID.
18
+ - **Short description** = the `Task` cell.
19
+ - Omit the `Evidence` column. Do not render the Frozen / Archive sections.
20
+
21
+ One row per open task. Never invent — show a `?` cell exactly as `?`.
@@ -16,9 +16,12 @@
16
16
  * 5. No duplicate IDs across the whole file.
17
17
  * 6. `INDEX.md` / `todo.md`, if present, are pointers (content referencing
18
18
  * BACKLOG.md), not live row lists.
19
+ * 7. `BACKLOG.md` is git-tracked (committed), not untracked — the board syncs
20
+ * across sessions only through git. Skipped outside a git repo.
19
21
  */
20
22
  const fs = require("fs");
21
23
  const path = require("path");
24
+ const { execFileSync } = require("child_process");
22
25
 
23
26
  const root = path.resolve(__dirname, "..", "..");
24
27
 
@@ -41,6 +44,40 @@ if (!fs.existsSync(canonical)) {
41
44
  }
42
45
  check(true, "canonical BACKLOG.md present at repo root");
43
46
 
47
+ // ---- 7. BACKLOG.md must be git-tracked (committed), not untracked ----------
48
+ // The board syncs across sessions only through git; an untracked BACKLOG.md
49
+ // never reaches the shared board. Skipped outside a git repo.
50
+ function insideGitRepo() {
51
+ try {
52
+ const r = execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
53
+ cwd: root,
54
+ stdio: "pipe",
55
+ });
56
+ return (r.toString() || "").trim() === "true";
57
+ } catch (_) {
58
+ return false;
59
+ }
60
+ }
61
+ function gitTracked(rel) {
62
+ try {
63
+ const r = execFileSync("git", ["ls-files", "--error-unmatch", rel], {
64
+ cwd: root,
65
+ stdio: "pipe",
66
+ });
67
+ return (r.toString() || "").trim().length > 0;
68
+ } catch (_) {
69
+ return false;
70
+ }
71
+ }
72
+ if (insideGitRepo()) {
73
+ check(
74
+ gitTracked("BACKLOG.md"),
75
+ "BACKLOG.md is git-tracked (run `git add BACKLOG.md` then commit — an untracked board never syncs)"
76
+ );
77
+ } else {
78
+ check(true, "not a git repo \u2014 skipping BACKLOG.md tracking check");
79
+ }
80
+
44
81
  const src = fs.readFileSync(canonical, "utf8");
45
82
  const lines = src.split(/\r?\n/);
46
83