@aihq/harness 0.2.0 → 0.3.1

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/dist/cli.js CHANGED
@@ -1,12 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- buildProgram
4
- } from "./chunk-2P5QRFQK.js";
5
-
6
- // src/cli.ts
7
- buildProgram().parseAsync(process.argv).catch((err) => {
8
- process.stderr.write(`fatal: ${err instanceof Error ? err.message : String(err)}
9
- `);
10
- process.exitCode = 1;
11
- });
12
- //# sourceMappingURL=cli.js.map
2
+ import{ja as s}from"./chunk-EAFL7TH3.js";s().parseAsync(process.argv).catch(r=>{process.stderr.write(`fatal: ${r instanceof Error?r.message:String(r)}
3
+ `),process.exitCode=1});
package/dist/index.d.ts CHANGED
@@ -186,7 +186,7 @@ type Verdict = "pass" | "fail" | "skip";
186
186
  * sealed: a new failure mode means a new member here PLUS the `code` set at the
187
187
  * emitter; never derive a code by matching `detail`.
188
188
  */
189
- type CheckCode = "env.node-runtime" | "env.git-missing" | "env.dev-tool-missing" | "env.tool-install-blocked" | "cert.ca-missing" | "tls.verify-failed" | "npm.runtime-broken" | "path.missing" | "mcp.blocked" | "mcp.uv-missing" | "mcp.config-missing" | "mcp.unvendored-offline" | "mcp.policy-denied" | "mcp.hardcoded-secret" | "mcp.allowlist-drift" | "cli.not-detected" | "cli.config-only" | "cli.bootloader-missing" | "cli.bootloader-drift" | "cli.wont-load" | "canon.router-missing" | "canon.context-dir-missing" | "canon.lint-failed" | "canon.adoptable" | "canon.cli-native-unmigrated" | "secrets.plaintext-detected" | "guardrails.gitleaks-missing" | "usage.no-data" | "scale.code-review-graph-missing" | "contract.path-unportable" | "contract.stale" | "org-policy.drift" | "report.context-over-budget" | "report.low-adoption" | "report.contract-untrue" | "trust.fetch-blocked" | "trust.detector-unavailable" | "trust.hidden-unicode" | "trust.prompt-injection" | "trust.source-changed" | "trust.auto-exec-hook" | "trust.dependency-confusion" | "trust.typosquat" | "trust.malicious-code" | "trust.source-drift" | "trust.unpinned-dependency" | "trust.untrusted-publisher" | "trust.unsigned-source";
189
+ type CheckCode = "env.node-runtime" | "env.git-missing" | "env.dev-tool-missing" | "env.tool-install-blocked" | "cert.ca-missing" | "tls.verify-failed" | "npm.runtime-broken" | "path.missing" | "mcp.blocked" | "mcp.uv-missing" | "mcp.config-missing" | "mcp.unvendored-offline" | "mcp.policy-denied" | "mcp.hardcoded-secret" | "mcp.allowlist-drift" | "cli.not-detected" | "cli.config-only" | "cli.bootloader-missing" | "cli.bootloader-drift" | "cli.wont-load" | "canon.router-missing" | "canon.context-dir-missing" | "canon.lint-failed" | "canon.adoptable" | "canon.cli-native-unmigrated" | "secrets.plaintext-detected" | "guardrails.gitleaks-missing" | "usage.no-data" | "scale.code-review-graph-missing" | "contract.path-unportable" | "contract.stale" | "org-policy.drift" | "report.context-over-budget" | "report.low-adoption" | "report.contract-untrue" | "ready.blocked" | "trust.fetch-blocked" | "trust.detector-unavailable" | "trust.hidden-unicode" | "trust.prompt-injection" | "trust.source-changed" | "trust.auto-exec-hook" | "trust.dependency-confusion" | "trust.typosquat" | "trust.malicious-code" | "trust.source-drift" | "trust.unpinned-dependency" | "trust.untrusted-publisher" | "trust.unsigned-source" | "trust.license-missing";
190
190
  interface Check {
191
191
  name: string;
192
192
  verdict: Verdict;
@@ -246,10 +246,14 @@ declare class VerificationReport {
246
246
  * - `digest`: a read-only computed result printed verbatim (an analytics
247
247
  * report / roll-up) plus optional structured `data` echoed into
248
248
  * `--json` — mutates nothing, never contacts a remote system.
249
+ * - `remove`: delete a repo-LOCAL file aih exclusively owns (a stale per-CLI
250
+ * adapter / kiro extra when its CLI is dropped), reversibly by
251
+ * default (moved to gitignored `.aih/legacy/`). Fail-closed:
252
+ * contained, symlink-guarded, backed up before unlink. Never remote.
249
253
  * Because no action kind can mutate a remote system, an autonomous run cannot
250
254
  * "fake provisioning" — the capability simply does not exist.
251
255
  */
252
- type ActionKind = "write" | "probe" | "doc" | "exec" | "envblock" | "digest";
256
+ type ActionKind = "write" | "probe" | "doc" | "exec" | "envblock" | "digest" | "remove";
253
257
  interface WriteAction {
254
258
  kind: "write";
255
259
  path: string;
@@ -346,7 +350,27 @@ interface DigestAction {
346
350
  data?: unknown;
347
351
  };
348
352
  }
349
- type Action = WriteAction | DocAction | ProbeAction | ExecAction | EnvBlockAction | DigestAction;
353
+ /**
354
+ * Remove a repo-LOCAL file that aih exclusively owns — aih's only destructive
355
+ * action. Emitted solely by `aih prune` for artifacts its detection proved
356
+ * aih-owned (a per-CLI adapter note, a kiro steering/hook extra) once the CLI is
357
+ * dropped. The executor fails closed: mandatory {@link assertContained} on the raw
358
+ * path (no `external` field exists, so a global `~/home` file is structurally
359
+ * unreachable), a symlink guard, and a backup before unlink. By default it MOVES the
360
+ * file to gitignored `.aih/legacy/<path>` (reversible; occupied destinations are never
361
+ * overwritten). Under `hardDelete` it instead renames to the sibling `<path>.aih.bak`
362
+ * — the same single-slot, latest-wins backup every aih write gets — for users who
363
+ * explicitly opt out of the archive.
364
+ */
365
+ interface RemoveAction {
366
+ kind: "remove";
367
+ /** Repo-relative path of the file to remove. */
368
+ path: string;
369
+ describe: string;
370
+ /** Opt-in: single-slot `<path>.aih.bak` rename instead of the `.aih/legacy/` archive. */
371
+ hardDelete?: boolean;
372
+ }
373
+ type Action = WriteAction | DocAction | ProbeAction | ExecAction | EnvBlockAction | DigestAction | RemoveAction;
350
374
  interface Plan {
351
375
  capability: string;
352
376
  actions: Action[];
@@ -366,6 +390,13 @@ interface PlanContext {
366
390
  /** When true, probe actions run and contribute to the verification report. */
367
391
  verify: boolean;
368
392
  json: boolean;
393
+ /**
394
+ * Local process runner. During `plan()` (dry-run) only READ-ONLY tools on FIXED targets
395
+ * may be run, and only to DECIDE the plan (heal's node/npm/TLS checks pick the repair
396
+ * ladder; certs reads the OS trust store; report shells `git` for stats). Never shell out
397
+ * an arbitrary or interpolated command at plan time — that is the `AIH_GRAPH_CMD` class of
398
+ * bug. The read-only allowlist is pinned by `tests/internals/plan-purity.test.ts` (#35).
399
+ */
369
400
  run: Runner;
370
401
  host: HostAdapter;
371
402
  env: NodeJS.ProcessEnv;
@@ -413,6 +444,13 @@ interface CommandSpec {
413
444
  * blocking the report on a dirty tree is wrong.
414
445
  */
415
446
  skipWorktreeGate?: boolean;
447
+ /**
448
+ * Wire an interactive prompter for this command in a TTY even without `--detect`,
449
+ * so a bare run can offer a confirmation (e.g. `aih ready` asking to install the
450
+ * missing core tools). Still suppressed under `--json`/`--yes`/non-TTY, so
451
+ * automation stays non-interactive.
452
+ */
453
+ wantsInstallPrompt?: boolean;
416
454
  }
417
455
  declare function writeText(path: string, contents: string, describe: string, opts?: {
418
456
  mode?: number;
@@ -437,6 +475,9 @@ declare function exec(describe: string, argv: string[], opts?: {
437
475
  blockProbesOnFailure?: boolean;
438
476
  }): ExecAction;
439
477
  declare function envBlock(path: string, scope: string, shell: EnvShell, vars: EnvVar[], describe: string): EnvBlockAction;
478
+ declare function remove(path: string, describe: string, opts?: {
479
+ hardDelete?: boolean;
480
+ }): RemoveAction;
440
481
  declare function plan(capability: string, ...actions: Action[]): Plan;
441
482
 
442
483
  /** Capability commands (repo/workstation mutators), dry-run by default. */
@@ -520,6 +561,15 @@ interface WriteSummary {
520
561
  */
521
562
  effect: "create" | "overwrite" | "merge" | "unchanged" | "kept";
522
563
  }
564
+ interface RemoveSummary {
565
+ path: string;
566
+ describe: string;
567
+ /** `remove` = move to `.aih/legacy/`; `delete` = hard-delete (single-slot `.aih.bak`
568
+ * backup); `absent` = nothing on disk. */
569
+ effect: "remove" | "delete" | "absent";
570
+ /** Repo-relative destination (`.aih/legacy/…` or `<path>.aih.bak`), when present. */
571
+ to?: string;
572
+ }
523
573
  interface PlanResult {
524
574
  capability: string;
525
575
  applied: boolean;
@@ -545,6 +595,8 @@ interface PlanResult {
545
595
  data?: unknown;
546
596
  }[];
547
597
  backups: string[];
598
+ /** Files aih removed (moved to `.aih/legacy/`) or would remove (dry-run). */
599
+ removed: RemoveSummary[];
548
600
  report?: VerificationReport;
549
601
  }
550
602
  /**
@@ -601,9 +653,15 @@ interface StagedWrite {
601
653
  contents: string;
602
654
  mode?: number;
603
655
  }
656
+ interface AppliedRemoval {
657
+ path: string;
658
+ legacyPath: string;
659
+ }
604
660
  interface FsTxnResult {
605
661
  written: string[];
606
662
  backups: string[];
663
+ /** Files moved out of the tree (source → `.aih/legacy/` destination). */
664
+ removed: AppliedRemoval[];
607
665
  }
608
666
  /**
609
667
  * Stages writes in memory and commits them atomically. Each existing target is
@@ -614,7 +672,20 @@ interface FsTxnResult {
614
672
  */
615
673
  declare class FsTransaction {
616
674
  private staged;
675
+ private stagedRemovals;
617
676
  stage(path: string, contents: string, mode?: number): void;
677
+ /**
678
+ * Stage a file REMOVAL as a reversible move to `legacyPath` (under gitignored
679
+ * `.aih/legacy/`). The move IS the backup: rollback (and the user) restore by
680
+ * moving it back. Symlinks are refused at commit (moving a link then restoring it
681
+ * would recreate a regular file). No-op if the source is already gone.
682
+ * `backupSibling` marks a hard-delete destination (`<path>.aih.bak`): still
683
+ * never-overwrite, but a taken slot falls back to `<path>.N.aih.bak` (matches the
684
+ * gitignored `*.aih.bak` glob) instead of the archive's `<path>.N`.
685
+ */
686
+ stageRemoval(path: string, legacyPath: string, opts?: {
687
+ backupSibling?: boolean;
688
+ }): void;
618
689
  preview(): ReadonlyArray<StagedWrite>;
619
690
  commit(): FsTxnResult;
620
691
  }
@@ -699,8 +770,8 @@ declare function parseCertLines(stdout: string): CertEntry[];
699
770
  */
700
771
  declare function parsePemBlocks(stdout: string, subject?: string): CertEntry[];
701
772
 
702
- declare const VERSION = "0.2.0";
773
+ declare const VERSION = "0.3.1";
703
774
  /** Build the configured commander program. Imported by both the CLI entry and tests. */
704
775
  declare function buildProgram(): Command;
705
776
 
706
- export { ALL_COMMANDS, type AccelBackend, type Action, type ActionKind, type AdapterFactory, AihError, CAPABILITIES, type CertEntry, type Check, type CheckCode, type CommandOption, type CommandSpec, ContextDir, type DigestAction, DirtyWorktreeError, type DocAction, type EnvBlockAction, type EnvShell, type EnvVar, type ExecAction, FsTransaction, FsTxnError, type FsTxnResult, type GpuInfo, type GpuVendor, type HostAdapter, type HostAdapterOptions, MergeError, NotImplementedError, PathContainmentError, type Plan, type PlanContext, type PlanFn, type PlanResult, type Platform, PlatformError, type ProbeAction, READONLY, type RunOptions, type RunResult, type Runner, type Settings, SettingsError, VERSION, type VdiInfo, type Verdict, VerificationError, VerificationReport, type WriteAction, type WriteSummary, beginMarker, buildProgram, deepMerge, defaultRunner, derBase64ToPem, digest, doc, dynamicDigest, endMarker, ensureTrailingNewline, envBlock, exec, executePlan, fakeRunner, formatExport, frontmatter, indent, isPlainObject, jsonFile, lines, loadSettings, makeHostAdapter, managedBlock, missingToolRunner, parseCertLines, parseFirstInt, parseJsoncText, parseNvidiaSmi, parsePemBlocks, plan, probe, probeMany, readIfExists, registerCommands, removeManagedBlock, resolveContents, resolvePlatform, retryTransient, safeCaPattern, stripTrailingNewlines, summarizeResult, upsertManagedBlock, upsertTextBlock, vdiFromEnv, writeArtifact, writeJson, writeText };
777
+ export { ALL_COMMANDS, type AccelBackend, type Action, type ActionKind, type AdapterFactory, AihError, CAPABILITIES, type CertEntry, type Check, type CheckCode, type CommandOption, type CommandSpec, ContextDir, type DigestAction, DirtyWorktreeError, type DocAction, type EnvBlockAction, type EnvShell, type EnvVar, type ExecAction, FsTransaction, FsTxnError, type FsTxnResult, type GpuInfo, type GpuVendor, type HostAdapter, type HostAdapterOptions, MergeError, NotImplementedError, PathContainmentError, type Plan, type PlanContext, type PlanFn, type PlanResult, type Platform, PlatformError, type ProbeAction, READONLY, type RemoveAction, type RemoveSummary, type RunOptions, type RunResult, type Runner, type Settings, SettingsError, VERSION, type VdiInfo, type Verdict, VerificationError, VerificationReport, type WriteAction, type WriteSummary, beginMarker, buildProgram, deepMerge, defaultRunner, derBase64ToPem, digest, doc, dynamicDigest, endMarker, ensureTrailingNewline, envBlock, exec, executePlan, fakeRunner, formatExport, frontmatter, indent, isPlainObject, jsonFile, lines, loadSettings, makeHostAdapter, managedBlock, missingToolRunner, parseCertLines, parseFirstInt, parseJsoncText, parseNvidiaSmi, parsePemBlocks, plan, probe, probeMany, readIfExists, registerCommands, remove, removeManagedBlock, resolveContents, resolvePlatform, retryTransient, safeCaPattern, stripTrailingNewlines, summarizeResult, upsertManagedBlock, upsertTextBlock, vdiFromEnv, writeArtifact, writeJson, writeText };
package/dist/index.js CHANGED
@@ -1,131 +1 @@
1
- import {
2
- ALL_COMMANDS,
3
- AihError,
4
- CAPABILITIES,
5
- ContextDir,
6
- DirtyWorktreeError,
7
- FsTransaction,
8
- FsTxnError,
9
- MergeError,
10
- NotImplementedError,
11
- PathContainmentError,
12
- PlatformError,
13
- READONLY,
14
- SettingsError,
15
- VERSION,
16
- VerificationError,
17
- VerificationReport,
18
- beginMarker,
19
- buildProgram,
20
- deepMerge,
21
- defaultRunner,
22
- derBase64ToPem,
23
- digest,
24
- doc,
25
- dynamicDigest,
26
- endMarker,
27
- ensureTrailingNewline,
28
- envBlock,
29
- exec,
30
- executePlan,
31
- fakeRunner,
32
- formatExport,
33
- frontmatter,
34
- indent,
35
- isPlainObject,
36
- jsonFile,
37
- lines,
38
- loadSettings,
39
- makeHostAdapter,
40
- managedBlock,
41
- missingToolRunner,
42
- parseCertLines,
43
- parseFirstInt,
44
- parseJsoncText,
45
- parseNvidiaSmi,
46
- parsePemBlocks,
47
- plan,
48
- probe,
49
- probeMany,
50
- readIfExists,
51
- registerCommands,
52
- removeManagedBlock,
53
- resolveContents,
54
- resolvePlatform,
55
- retryTransient,
56
- safeCaPattern,
57
- stripTrailingNewlines,
58
- summarizeResult,
59
- upsertManagedBlock,
60
- upsertTextBlock,
61
- vdiFromEnv,
62
- writeArtifact,
63
- writeJson,
64
- writeText
65
- } from "./chunk-2P5QRFQK.js";
66
- export {
67
- ALL_COMMANDS,
68
- AihError,
69
- CAPABILITIES,
70
- ContextDir,
71
- DirtyWorktreeError,
72
- FsTransaction,
73
- FsTxnError,
74
- MergeError,
75
- NotImplementedError,
76
- PathContainmentError,
77
- PlatformError,
78
- READONLY,
79
- SettingsError,
80
- VERSION,
81
- VerificationError,
82
- VerificationReport,
83
- beginMarker,
84
- buildProgram,
85
- deepMerge,
86
- defaultRunner,
87
- derBase64ToPem,
88
- digest,
89
- doc,
90
- dynamicDigest,
91
- endMarker,
92
- ensureTrailingNewline,
93
- envBlock,
94
- exec,
95
- executePlan,
96
- fakeRunner,
97
- formatExport,
98
- frontmatter,
99
- indent,
100
- isPlainObject,
101
- jsonFile,
102
- lines,
103
- loadSettings,
104
- makeHostAdapter,
105
- managedBlock,
106
- missingToolRunner,
107
- parseCertLines,
108
- parseFirstInt,
109
- parseJsoncText,
110
- parseNvidiaSmi,
111
- parsePemBlocks,
112
- plan,
113
- probe,
114
- probeMany,
115
- readIfExists,
116
- registerCommands,
117
- removeManagedBlock,
118
- resolveContents,
119
- resolvePlatform,
120
- retryTransient,
121
- safeCaPattern,
122
- stripTrailingNewlines,
123
- summarizeResult,
124
- upsertManagedBlock,
125
- upsertTextBlock,
126
- vdiFromEnv,
127
- writeArtifact,
128
- writeJson,
129
- writeText
130
- };
131
- //# sourceMappingURL=index.js.map
1
+ import{$ as fr,A as Y,B as _,C as c,D as h,E as j,F as k,G as q,H as v,I as w,J as y,K as z,L as F,M as G,N as H,O as J,P as K,Q,R as U,S as W,T as X,U as Z,V as $,W as rr,X as or,Y as mr,Z as er,_ as tr,a as p,aa as pr,b as x,ba as xr,c as A,ca as Ar,d as I,da as Ir,e as L,ea as r,f as C,fa as o,g as E,ga as m,h as N,ha as e,i as O,ia as t,j as S,ja as f,k as a,l as d,m as g,n as i,o as s,p as D,q as M,r as P,s as R,t as b,u as l,v as n,w as u,x as B,y as T,z as V}from"./chunk-EAFL7TH3.js";export{m as ALL_COMMANDS,p as AihError,r as CAPABILITIES,g as ContextDir,O as DirtyWorktreeError,a as FsTransaction,I as FsTxnError,E as MergeError,C as NotImplementedError,N as PathContainmentError,A as PlatformError,o as READONLY,x as SettingsError,t as VERSION,L as VerificationError,K as VerificationReport,k as beginMarker,f as buildProgram,J as deepMerge,Z as defaultRunner,or as derBase64ToPem,P as digest,M as doc,R as dynamicDigest,q as endMarker,j as ensureTrailingNewline,u as envBlock,n as exec,W as executePlan,$ as fakeRunner,w as formatExport,c as frontmatter,_ as indent,H as isPlainObject,h as jsonFile,Y as lines,i as loadSettings,Ir as makeHostAdapter,v as managedBlock,rr as missingToolRunner,pr as parseCertLines,tr as parseFirstInt,G as parseJsoncText,fr as parseNvidiaSmi,xr as parsePemBlocks,T as plan,b as probe,l as probeMany,d as readIfExists,e as registerCommands,B as remove,F as removeManagedBlock,U as resolveContents,Ar as resolvePlatform,S as retryTransient,mr as safeCaPattern,V as stripTrailingNewlines,X as summarizeResult,y as upsertManagedBlock,z as upsertTextBlock,er as vdiFromEnv,Q as writeArtifact,D as writeJson,s as writeText};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aihq/harness",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Enterprise AI Bootstrapping Harness — bootstraps governed, proxy-safe AI coding into workstations and repos",
5
5
  "repository": {
6
6
  "type": "git",
@@ -58,6 +58,8 @@
58
58
  "@biomejs/biome": "^2.5.1",
59
59
  "@types/node": "^26.0.1",
60
60
  "@vitest/coverage-v8": "^4.1.9",
61
+ "ajv": "^8.20.0",
62
+ "ajv-formats": "^3.0.1",
61
63
  "happy-dom": "^20.10.6",
62
64
  "tsup": "^8.5.1",
63
65
  "tsx": "^4.22.4",