harnery 0.6.0 → 0.7.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/README.md +16 -6
- package/dist/commander.d.ts +19 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +2 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +51 -4
- package/dist/commands/deinit.d.ts.map +1 -1
- package/dist/commands/deinit.js +4 -0
- package/dist/commands/devtools.d.ts +4 -0
- package/dist/commands/devtools.d.ts.map +1 -0
- package/dist/commands/devtools.js +239 -0
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +69 -1
- package/dist/commands/doctor.js +12 -4
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +3 -63
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +54 -14
- package/dist/commands/scratch.js +1 -1
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +273 -62
- package/dist/commands/web-fetch.js +1 -1
- package/dist/core/agents/cli.js +48 -0
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +32 -8
- package/dist/core/agents/events/emit.d.ts.map +1 -1
- package/dist/core/agents/events/emit.js +4 -0
- package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/claim-conflict.js +16 -5
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-projector.js +10 -3
- package/dist/core/config.d.ts +10 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +13 -0
- package/dist/core/hooks/cli.js +3 -3
- package/dist/core/hooks/effects/index.d.ts +11 -7
- package/dist/core/hooks/effects/index.d.ts.map +1 -1
- package/dist/core/hooks/effects/index.js +15 -18
- package/dist/core/hooks/events/emit.d.ts.map +1 -1
- package/dist/core/hooks/events/emit.js +4 -0
- package/dist/core/hooks/events/rotate.d.ts +43 -0
- package/dist/core/hooks/events/rotate.d.ts.map +1 -0
- package/dist/core/hooks/events/rotate.js +142 -0
- package/dist/core/hooks/harness/events.d.ts +11 -1
- package/dist/core/hooks/harness/events.d.ts.map +1 -1
- package/dist/core/hooks/harness/events.js +22 -3
- package/dist/core/hooks/harness/wiring.d.ts +8 -0
- package/dist/core/hooks/harness/wiring.d.ts.map +1 -1
- package/dist/core/hooks/harness/wiring.js +34 -5
- package/dist/core/scratch/index.d.ts.map +1 -0
- package/dist/{lib → core}/scratch/index.js +2 -2
- package/dist/lib/devtools.d.ts +178 -0
- package/dist/lib/devtools.d.ts.map +1 -0
- package/dist/lib/devtools.js +1328 -0
- package/dist/lib/docs-frontmatter-migrate.d.ts +33 -0
- package/dist/lib/docs-frontmatter-migrate.d.ts.map +1 -0
- package/dist/lib/docs-frontmatter-migrate.js +364 -0
- package/dist/lib/docs-frontmatter.d.ts +33 -0
- package/dist/lib/docs-frontmatter.d.ts.map +1 -0
- package/dist/lib/docs-frontmatter.js +130 -0
- package/dist/lib/docs-index.d.ts +1 -0
- package/dist/lib/docs-index.d.ts.map +1 -1
- package/dist/lib/docs-index.js +4 -5
- package/dist/lib/docs-lint.d.ts +2 -0
- package/dist/lib/docs-lint.d.ts.map +1 -1
- package/dist/lib/docs-lint.js +18 -12
- package/dist/lib/docs-meta.d.ts +14 -0
- package/dist/lib/docs-meta.d.ts.map +1 -0
- package/dist/lib/docs-meta.js +34 -0
- package/dist/lib/docs-sweep.d.ts +12 -0
- package/dist/lib/docs-sweep.d.ts.map +1 -1
- package/dist/lib/docs-sweep.js +98 -103
- package/dist/lib/format.js +2 -2
- package/dist/lib/http/index.d.ts +1 -0
- package/dist/lib/http/index.d.ts.map +1 -1
- package/dist/lib/http/index.js +1 -0
- package/dist/lib/http/request.d.ts +77 -0
- package/dist/lib/http/request.d.ts.map +1 -0
- package/dist/lib/http/request.js +105 -0
- package/dist/lib/instructions/apply.d.ts +63 -0
- package/dist/lib/instructions/apply.d.ts.map +1 -0
- package/dist/lib/instructions/apply.js +255 -0
- package/dist/lib/instructions/splice.d.ts +73 -0
- package/dist/lib/instructions/splice.d.ts.map +1 -0
- package/dist/lib/instructions/splice.js +118 -0
- package/dist/lib/instructions/templates.d.ts +45 -0
- package/dist/lib/instructions/templates.d.ts.map +1 -0
- package/dist/lib/instructions/templates.js +258 -0
- package/dist/lib/tunnel/gate.d.ts +1 -0
- package/dist/lib/tunnel/gate.d.ts.map +1 -1
- package/dist/lib/tunnel/gate.js +14 -9
- package/dist/lib/tunnel/state.d.ts +11 -1
- package/dist/lib/tunnel/state.d.ts.map +1 -1
- package/dist/lib/tunnel/state.js +8 -3
- package/package.json +7 -6
- package/src/commander.ts +23 -0
- package/src/commands/agents.ts +50 -3
- package/src/commands/deinit.ts +5 -0
- package/src/commands/devtools.ts +284 -0
- package/src/commands/docs.ts +81 -1
- package/src/commands/doctor.ts +13 -4
- package/src/commands/env.ts +11 -77
- package/src/commands/init.ts +66 -15
- package/src/commands/scratch.ts +1 -1
- package/src/commands/tunnel.ts +316 -65
- package/src/commands/web-fetch.ts +1 -1
- package/src/core/agents/cli.ts +55 -0
- package/src/core/agents/coord-client.ts +34 -7
- package/src/core/agents/events/emit.ts +5 -0
- package/src/core/agents/rules/claim-conflict.ts +17 -6
- package/src/core/agents/state/heartbeat-projector.ts +11 -3
- package/src/core/config.ts +14 -0
- package/src/core/hooks/cli.ts +3 -3
- package/src/core/hooks/effects/index.ts +23 -17
- package/src/core/hooks/events/emit.ts +5 -0
- package/src/core/hooks/events/rotate.ts +151 -0
- package/src/core/hooks/harness/events.ts +30 -3
- package/src/core/hooks/harness/wiring.ts +46 -5
- package/src/{lib → core}/scratch/index.ts +2 -2
- package/src/lib/devtools.ts +1653 -0
- package/src/lib/docs-frontmatter-migrate.ts +427 -0
- package/src/lib/docs-frontmatter.ts +151 -0
- package/src/lib/docs-index.ts +4 -5
- package/src/lib/docs-lint.ts +17 -11
- package/src/lib/docs-meta.ts +44 -0
- package/src/lib/docs-sweep.ts +104 -102
- package/src/lib/format.ts +2 -2
- package/src/lib/http/index.ts +1 -0
- package/src/lib/http/request.ts +154 -0
- package/src/lib/instructions/apply.ts +318 -0
- package/src/lib/instructions/splice.ts +148 -0
- package/src/lib/instructions/templates.ts +295 -0
- package/src/lib/tunnel/gate.ts +14 -9
- package/src/lib/tunnel/state.ts +19 -4
- package/dist/lib/scratch/index.d.ts.map +0 -1
- /package/dist/{lib → core}/scratch/index.d.ts +0 -0
package/README.md
CHANGED
|
@@ -7,23 +7,24 @@
|
|
|
7
7
|
|
|
8
8
|
# Harnery
|
|
9
9
|
|
|
10
|
-
> Multi-agent coordination
|
|
10
|
+
> Multi-agent coordination for AI coding agents — Claude Code, Cursor, and Codex.
|
|
11
11
|
|
|
12
12
|
[](https://github.com/ryanjkelly/harnery/actions/workflows/ci.yml)
|
|
13
13
|
[](https://www.npmjs.com/package/harnery)
|
|
14
14
|
[](LICENSE)
|
|
15
15
|
|
|
16
|
-
> ⚠️ **Pre-1.0.** API surface is still settling. Pin a specific minor version (`harnery@^0.
|
|
16
|
+
> ⚠️ **Pre-1.0.** API surface is still settling. Pin a specific minor version (`harnery@^0.7.0`) and read the [CHANGELOG](CHANGELOG.md) before each upgrade.
|
|
17
17
|
|
|
18
18
|
## What it is
|
|
19
19
|
|
|
20
|
-
`harnery`
|
|
20
|
+
`harnery` keeps multiple AI coding agents from stepping on each other in a shared checkout. It was extracted from years of running several Claude Code / Cursor / Codex sessions at once against the same monorepo:
|
|
21
21
|
|
|
22
|
-
- **Multi-agent coordination:** per-agent heartbeats in `.harnery/active/`, claim-time and commit-time guards, the canonical event stream, harness adapters for Claude Code / Cursor / Codex.
|
|
23
|
-
- **
|
|
24
|
-
- **Standalone web UI:** `harn web up` boots a local Next.js dashboard for the coord layer, councils, and per-project state. Ships with the git clone, not the npm package (see [Install](#install)).
|
|
22
|
+
- **Multi-agent coordination:** per-agent heartbeats in `.harnery/active/`, claim-time and commit-time guards, the canonical event stream, councils, a decision docket, per-agent scratchpads, and harness adapters for Claude Code / Cursor / Codex.
|
|
23
|
+
- **Standalone web UI:** `harn web up` boots a local Next.js dashboard for the coord layer, councils, per-project state, and a local coding-agent status view (Claude Code / Codex / Cursor usage, backed by `harn devtools`). Ships with the git clone, not the npm package (see [Install](#install)).
|
|
25
24
|
- **Backup + sync:** `harn backup` snapshots `.harnery/` via [restic](https://restic.net/); `harn sync` keeps a curated subset live across machines via [rclone](https://rclone.org/) (Google Drive or any rclone remote).
|
|
26
25
|
|
|
26
|
+
The CLI also ships batteries: portable utility commands (`tokens`, `eml`, `env`, `grep`, `docs`, `browse`, `fetch`, `read`, `devtools`, and more) plus the library toolkit they're built from. Useful, cross-platform, dependency-light — and deliberately not the headline. Coordination is why harnery exists; the toolkit exists because the CLI needed it (see [Public surface tiers](#public-surface-tiers)).
|
|
27
|
+
|
|
27
28
|
## Install
|
|
28
29
|
|
|
29
30
|
```bash
|
|
@@ -80,6 +81,15 @@ await program.parseAsync(process.argv);
|
|
|
80
81
|
|
|
81
82
|
`mycli agents status` then resolves to **the same code** as `harn agents status`, loaded as a library. See [examples/extending-with-commander.ts](examples/extending-with-commander.ts) for the full pattern.
|
|
82
83
|
|
|
84
|
+
### Public surface tiers
|
|
85
|
+
|
|
86
|
+
The exports map draws the line between what harnery *is* and what it *ships with*:
|
|
87
|
+
|
|
88
|
+
- **Product tier** — `harnery`, `harnery/commander`, `harnery/core/*`: the coordination layer and CLI composition. This is the API to build against, and the reason to install harnery.
|
|
89
|
+
- **Toolkit tier** — every `harnery/lib/*` subpath (`http`, `cookies`, `format`, `readability`, `browser`, `machine`, …): the supporting utilities harnery's own CLI is built from, exposed for embedding hosts that want to lean on them. Supported, but secondary: it can evolve faster than the product tier, and it isn't a reason to adopt harnery on its own.
|
|
90
|
+
|
|
91
|
+
The boundary is enforced, not aspirational: CI verifies that no `harnery/lib/*` export imports the coordination core, directly or transitively (`scripts/check-layering.ts`), so pulling a toolkit module never drags in coordination state. Details: [Embedding + surface tiers](https://harnery.com/concepts/embedding/).
|
|
92
|
+
|
|
83
93
|
## Documentation
|
|
84
94
|
|
|
85
95
|
Full docs at **[harnery.com](https://harnery.com)**:
|
package/dist/commander.d.ts
CHANGED
|
@@ -42,6 +42,18 @@ export interface HarneryContextOpts {
|
|
|
42
42
|
*/
|
|
43
43
|
skipCommands?: readonly string[];
|
|
44
44
|
}
|
|
45
|
+
/** One row in a `harn env` section report. */
|
|
46
|
+
export interface EnvCheck {
|
|
47
|
+
label: string;
|
|
48
|
+
value: string;
|
|
49
|
+
status?: "ok" | "missing" | "warn" | "info";
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A `harn env` section: an async probe returning its rows. Hosts register extra
|
|
53
|
+
* sections (e.g. cloud-provider connectivity) via `context.envSections`; harnery
|
|
54
|
+
* core ships only the generic sections (runtimes, docker, git).
|
|
55
|
+
*/
|
|
56
|
+
export type EnvSection = () => Promise<EnvCheck[]>;
|
|
45
57
|
export interface HarneryProgramContext {
|
|
46
58
|
/** Project name (e.g., "my-monorepo"). Used in user-facing log lines + telemetry tags. */
|
|
47
59
|
projectName?: string;
|
|
@@ -59,6 +71,13 @@ export interface HarneryProgramContext {
|
|
|
59
71
|
* submodule row.
|
|
60
72
|
*/
|
|
61
73
|
submodules?: readonly string[];
|
|
74
|
+
/**
|
|
75
|
+
* Extra `harn env` sections keyed by name (e.g. `{ gcp, bq }`). Merged into
|
|
76
|
+
* the built-in generic sections (runtimes, docker, git), so a host can expose
|
|
77
|
+
* `harn env <name>` for its own environment probes without harnery core
|
|
78
|
+
* carrying provider-specific checks. harn standalone ships none.
|
|
79
|
+
*/
|
|
80
|
+
envSections?: Record<string, EnvSection>;
|
|
62
81
|
/**
|
|
63
82
|
* Optional callback that returns extra HTTP headers to attach to outbound
|
|
64
83
|
* `fetch` calls based on the target URL. Useful for consumers that need
|
package/dist/commander.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commander.d.ts","sourceRoot":"","sources":["../src/commander.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"commander.d.ts","sourceRoot":"","sources":["../src/commander.ts"],"names":[],"mappings":"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"}
|
package/dist/commander.js
CHANGED
|
@@ -28,6 +28,7 @@ import { registerContextCommand } from "./commands/context.js";
|
|
|
28
28
|
import { registerCookiesCommand } from "./commands/cookies.js";
|
|
29
29
|
import { registerDecisionCommand } from "./commands/decision.js";
|
|
30
30
|
import { registerDeinitCommand } from "./commands/deinit.js";
|
|
31
|
+
import { registerDevtoolsCommand } from "./commands/devtools.js";
|
|
31
32
|
import { registerDocsCommand } from "./commands/docs.js";
|
|
32
33
|
import { registerDoctorCommand } from "./commands/doctor.js";
|
|
33
34
|
import { registerEditBatchCommand } from "./commands/edit-batch.js";
|
|
@@ -112,6 +113,7 @@ export function createHarneryProgram(opts = {}) {
|
|
|
112
113
|
registerContextCommand(program, emit, opts.context);
|
|
113
114
|
registerScratchCommand(program, emit);
|
|
114
115
|
registerDecisionCommand(program, emit);
|
|
116
|
+
registerDevtoolsCommand(program, emit);
|
|
115
117
|
registerTunnelCommand(program, emit, opts.context);
|
|
116
118
|
registerDocsCommand(program, emit, opts.context);
|
|
117
119
|
registerAgentsCommand(program, emit);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/commands/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAeH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/commands/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAeH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAwFnD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,GAAG,IAAI,CA4OpF"}
|
package/dist/commands/agents.js
CHANGED
|
@@ -17,16 +17,16 @@ import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync,
|
|
|
17
17
|
import { homedir, tmpdir } from "node:os";
|
|
18
18
|
import { basename, join, resolve } from "node:path";
|
|
19
19
|
import { readStreamTailBounded } from "../core/agents/events/consume.js";
|
|
20
|
-
import { emitCanonical, monorepoRoot, normalizeHarness, readHeartbeat, resolveOwner, resolveOwnerWithSource, } from "../core/agents/index.js";
|
|
20
|
+
import { emitCanonical, monorepoRoot, normalizeHarness, readHeartbeat, resolveOwner, resolveOwnerBySessionEnv, resolveOwnerWithSource, } from "../core/agents/index.js";
|
|
21
21
|
import { resolveBinName } from "../core/config.js";
|
|
22
22
|
/** Cap for CLI scans of the unbounded event ledger (`trace` / `health`). Well
|
|
23
23
|
* under V8's ~512MB max string length so a `readFileSync` of the whole file can
|
|
24
24
|
* never throw; covers ample recent history for a diagnostic scan. */
|
|
25
25
|
const STREAM_SCAN_CAP_BYTES = 128 * 1024 * 1024; // 128 MiB
|
|
26
26
|
import { parsePsChainLine } from "../core/hooks/resolve/anchor.js";
|
|
27
|
+
import { appendEntry, resolveOwnerByName } from "../core/scratch/index.js";
|
|
27
28
|
import { buildCouncilId, buildInviteMarkdown, COUNCIL_SCHEMA_VERSION, contributorsInRound, councilBodyDir, councilsArchiveDir, deleteArchivedCouncil, effectiveSteward, findManifestByPartialId, listKnownAgents, listManifests, moveFromArchive, moveToArchive, normalizeAgentName, pendingCouncilsForMember, readArchivedManifest, readManifest, readRoundPrompts, roundDir, setCouncilSteward, writeContribution, writeManifest, writePrompt, } from "../lib/council/index.js";
|
|
28
29
|
import { displayName as displayAgentName, ensureIdentity, listIdentities, lookupById as lookupIdentityById, lookupByName as lookupIdentityByName, } from "../lib/identities/index.js";
|
|
29
|
-
import { appendEntry, resolveOwnerByName } from "../lib/scratch/index.js";
|
|
30
30
|
const FRESHNESS_SECS = 600; // 10-minute heartbeat-freshness window.
|
|
31
31
|
const SUBAGENT_NOTE = "Bash identity is process-level in v1; if you're running inside a subagent, " +
|
|
32
32
|
"this resolves to the parent group's name, not the subagent's. A subagent-aware " +
|
|
@@ -80,8 +80,7 @@ export function registerAgentsCommand(program, emitParam) {
|
|
|
80
80
|
});
|
|
81
81
|
cmd
|
|
82
82
|
.command("show <name>")
|
|
83
|
-
.description("Deep-dive on one peer agent: registry state (files held, last tool, task) " +
|
|
84
|
-
"plus claude-sessions history (latest title, recent prompts, recent tools, tool-usage tallies). " +
|
|
83
|
+
.description("Deep-dive on one peer agent: registry state (files held, last tool, task, turn summary). " +
|
|
85
84
|
"Disambiguates name → instance_id via prefix match.")
|
|
86
85
|
.option("--json", "JSON envelope output")
|
|
87
86
|
.action(async (name, opts) => {
|
|
@@ -435,6 +434,7 @@ function runWhoami(opts) {
|
|
|
435
434
|
});
|
|
436
435
|
process.exit(1);
|
|
437
436
|
}
|
|
437
|
+
ensureCursorSession(root);
|
|
438
438
|
const resolved = resolveOwnerWithSource();
|
|
439
439
|
const myOwner = resolved.owner;
|
|
440
440
|
if (!myOwner) {
|
|
@@ -873,6 +873,49 @@ function formatLocalShort(iso) {
|
|
|
873
873
|
timeZone: "America/Chicago",
|
|
874
874
|
}).format(d);
|
|
875
875
|
}
|
|
876
|
+
function cursorEnvSessionId() {
|
|
877
|
+
const raw = process.env.CURSOR_SESSION_ID?.trim() || process.env.CURSOR_CONVERSATION_ID?.trim();
|
|
878
|
+
if (!raw)
|
|
879
|
+
return null;
|
|
880
|
+
return raw.startsWith("bc-") && raw.length > 3 ? raw.slice(3) : raw;
|
|
881
|
+
}
|
|
882
|
+
function shouldBootstrapCursorSession() {
|
|
883
|
+
return process.env.CURSOR_AGENT === "1" && cursorEnvSessionId() !== null;
|
|
884
|
+
}
|
|
885
|
+
function ensureCursorSession(root) {
|
|
886
|
+
if (!shouldBootstrapCursorSession())
|
|
887
|
+
return;
|
|
888
|
+
if (resolveOwnerBySessionEnv(root))
|
|
889
|
+
return;
|
|
890
|
+
const sessionId = cursorEnvSessionId();
|
|
891
|
+
if (!sessionId)
|
|
892
|
+
return;
|
|
893
|
+
const agentHook = resolve(root, "harnery", "bin", "agent-hook");
|
|
894
|
+
if (!existsSync(agentHook))
|
|
895
|
+
return;
|
|
896
|
+
const payload = JSON.stringify({
|
|
897
|
+
conversation_id: sessionId,
|
|
898
|
+
session_id: sessionId,
|
|
899
|
+
hook_event_name: "sessionStart",
|
|
900
|
+
workspace_roots: [root],
|
|
901
|
+
cwd: root,
|
|
902
|
+
composer_mode: "agent",
|
|
903
|
+
is_background_agent: false,
|
|
904
|
+
});
|
|
905
|
+
spawnSync("bash", [agentHook, "session-start", "--harness", "cursor"], {
|
|
906
|
+
input: payload,
|
|
907
|
+
cwd: root,
|
|
908
|
+
encoding: "utf8",
|
|
909
|
+
timeout: 3000,
|
|
910
|
+
env: {
|
|
911
|
+
...process.env,
|
|
912
|
+
HARNERY_AGENT_COORD_PLATFORM: "cursor",
|
|
913
|
+
HARNERY_COORD_ROOT_OVERRIDE: root,
|
|
914
|
+
CURSOR_SESSION_ID: sessionId,
|
|
915
|
+
CURSOR_CONVERSATION_ID: sessionId,
|
|
916
|
+
},
|
|
917
|
+
});
|
|
918
|
+
}
|
|
876
919
|
function runReleaseClaim(path) {
|
|
877
920
|
const root = monorepoRoot();
|
|
878
921
|
if (!root) {
|
|
@@ -920,6 +963,8 @@ function runSetTask(task, opts) {
|
|
|
920
963
|
// Identity: prefer explicit --session-id (the ppid-walk-free escape hatch,
|
|
921
964
|
// mirrors `status`), fall back to the ppid walk. Cursor shell tool calls
|
|
922
965
|
// don't descend from a pid-map-registered anchor, so the walk can miss there.
|
|
966
|
+
if (!opts?.sessionId)
|
|
967
|
+
ensureCursorSession(root);
|
|
923
968
|
const myOwner = opts?.sessionId ?? resolveOwner();
|
|
924
969
|
if (!myOwner) {
|
|
925
970
|
emit.error({
|
|
@@ -966,6 +1011,8 @@ function runStatus(opts) {
|
|
|
966
1011
|
}
|
|
967
1012
|
// Identity resolution: prefer explicit --session-id (hook-friendly), fall
|
|
968
1013
|
// back to ppid walk for interactive shell usage.
|
|
1014
|
+
if (!opts.sessionId)
|
|
1015
|
+
ensureCursorSession(root);
|
|
969
1016
|
const myOwner = opts.sessionId ?? resolveOwner();
|
|
970
1017
|
if (!myOwner) {
|
|
971
1018
|
emit.error({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deinit.d.ts","sourceRoot":"","sources":["../../src/commands/deinit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"deinit.d.ts","sourceRoot":"","sources":["../../src/commands/deinit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAanD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CA+GjG;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE;IACtC,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;CACtB,GAAG,OAAO,CAEV;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAM1C"}
|
package/dist/commands/deinit.js
CHANGED
|
@@ -31,6 +31,7 @@ import { relative, resolve } from "node:path";
|
|
|
31
31
|
import { createInterface } from "node:readline";
|
|
32
32
|
import { DEFAULT_BIN_NAME } from "../core/config.js";
|
|
33
33
|
import { HARNESS_SPECS } from "../core/hooks/harness/events.js";
|
|
34
|
+
import { removeInstructions } from "../lib/instructions/apply.js";
|
|
34
35
|
import { unwireHooks } from "./init.js";
|
|
35
36
|
export function registerDeinitCommand(program, emit, binName) {
|
|
36
37
|
// The interactive prompt + engine-removal hint are standalone-harn niceties.
|
|
@@ -113,6 +114,9 @@ export function registerDeinitCommand(program, emit, binName) {
|
|
|
113
114
|
`(${remaining} other hook(s) kept)`);
|
|
114
115
|
}
|
|
115
116
|
}
|
|
117
|
+
// ── 1b. agent-facing instructions block + skills ───────────────────────
|
|
118
|
+
const removed = removeInstructions(projectRoot, { harness, dryRun });
|
|
119
|
+
actions.push(...removed.actions, ...removed.warnings.map((w) => `! ${w}`));
|
|
116
120
|
// ── 2. coord root (opt-in; destructive) ────────────────────────────────
|
|
117
121
|
if (purgeState) {
|
|
118
122
|
if (!coordExists) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devtools.d.ts","sourceRoot":"","sources":["../../src/commands/devtools.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAkCnD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAsDjF"}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { chmodSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { cursorApiKeyPath, enrichFromApi, probeEndpoints, readDevtools, resolveCursorApiKey, } from "../lib/devtools.js";
|
|
4
|
+
/**
|
|
5
|
+
* `devtools`: report the local state of the AI coding agents harnery supports
|
|
6
|
+
* — Claude Code, Codex, and Cursor — in one place: logged-in status, plan/seat
|
|
7
|
+
* tier, auth expiry, session counts, and (where the tool keeps them locally)
|
|
8
|
+
* rate-limit / quota windows.
|
|
9
|
+
*
|
|
10
|
+
* Reads files on disk by default (no network). When a Cursor API key is
|
|
11
|
+
* configured (`devtools cursor-key set`), it additionally verifies the key and
|
|
12
|
+
* pulls Cloud Agent activity. `--usage` adds an opt-in windowed scan of local
|
|
13
|
+
* transcripts for approximate token totals.
|
|
14
|
+
*/
|
|
15
|
+
const VALID = ["claude-code", "codex", "cursor"];
|
|
16
|
+
export function registerDevtoolsCommand(program, emit) {
|
|
17
|
+
const cmd = program
|
|
18
|
+
.command("devtools")
|
|
19
|
+
.description("Local status of the AI coding agents (Claude Code, Codex, Cursor)")
|
|
20
|
+
.option("--tool <name>", "Restrict to a tool (repeatable): claude-code | codex | cursor", collect, [])
|
|
21
|
+
.option("--usage", "Also scan local transcripts for approximate token totals (slower)")
|
|
22
|
+
.option("--window-days <n>", "With --usage: only count transcripts modified within N days", (v) => Number.parseInt(v, 10), 7)
|
|
23
|
+
.option("--no-api", "Skip the Cursor API enrichment even when a key is configured")
|
|
24
|
+
.option("--format <type>", "Output format: table, json", "table")
|
|
25
|
+
.action(async (opts) => {
|
|
26
|
+
const only = (opts.tool ?? []).filter((t) => VALID.includes(t));
|
|
27
|
+
const bad = (opts.tool ?? []).filter((t) => !VALID.includes(t));
|
|
28
|
+
if (bad.length) {
|
|
29
|
+
emit.error({
|
|
30
|
+
code: "bad_tool",
|
|
31
|
+
message: `unknown --tool: ${bad.join(", ")}`,
|
|
32
|
+
hint: `valid: ${VALID.join(", ")}`,
|
|
33
|
+
});
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const report = readDevtools({
|
|
37
|
+
usage: opts.usage,
|
|
38
|
+
windowDays: opts.windowDays,
|
|
39
|
+
only: only.length ? only : undefined,
|
|
40
|
+
});
|
|
41
|
+
// Auto-enrich when a Cursor key is configured (the user opted in by
|
|
42
|
+
// storing one); --no-api forces pure-local.
|
|
43
|
+
if (opts.noApi !== true)
|
|
44
|
+
await enrichFromApi(report);
|
|
45
|
+
if (opts.format === "json") {
|
|
46
|
+
emit.config({ format: "json" });
|
|
47
|
+
emit.data({ ok: true, ...report });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
emit.text(renderTable(report.tools));
|
|
51
|
+
});
|
|
52
|
+
registerCursorKeyCommand(cmd, emit);
|
|
53
|
+
registerDoctorCommand(cmd, emit);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* `devtools doctor` — one live call per usage endpoint to check the integrations
|
|
57
|
+
* still work. Surfaces `auth_rejected` (our headers/token stopped being
|
|
58
|
+
* accepted) and `shape_changed` (response schema drifted) distinctly from a
|
|
59
|
+
* mere rate limit. Bypasses the cache; run occasionally, by hand.
|
|
60
|
+
*/
|
|
61
|
+
function registerDoctorCommand(parent, emit) {
|
|
62
|
+
parent
|
|
63
|
+
.command("doctor")
|
|
64
|
+
.description("Probe the usage endpoints once each to detect header/schema drift")
|
|
65
|
+
.option("--tool <name>", "Restrict to a tool (repeatable): claude-code | cursor", collect, [])
|
|
66
|
+
.option("--format <type>", "Output format: table, json", "table")
|
|
67
|
+
.action(async (opts) => {
|
|
68
|
+
const only = (opts.tool ?? []).filter((t) => VALID.includes(t));
|
|
69
|
+
const results = await probeEndpoints({ only: only.length ? only : undefined });
|
|
70
|
+
if (opts.format === "json") {
|
|
71
|
+
emit.config({ format: "json" });
|
|
72
|
+
emit.data({
|
|
73
|
+
ok: results.every((r) => r.outcome === "ok" || r.outcome === "rate_limited"),
|
|
74
|
+
results,
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
emit.text(renderDoctor(results));
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const DOCTOR_MARK = {
|
|
82
|
+
ok: "✓",
|
|
83
|
+
rate_limited: "~",
|
|
84
|
+
no_credential: "-",
|
|
85
|
+
auth_rejected: "✗",
|
|
86
|
+
shape_changed: "✗",
|
|
87
|
+
unreachable: "✗",
|
|
88
|
+
};
|
|
89
|
+
function renderDoctor(results) {
|
|
90
|
+
if (!results.length)
|
|
91
|
+
return "no probeable endpoints (Codex is local-only)";
|
|
92
|
+
const lines = [];
|
|
93
|
+
for (const r of results) {
|
|
94
|
+
lines.push(`${DOCTOR_MARK[r.outcome]} ${r.tool.padEnd(12)} ${r.outcome.padEnd(14)} ${r.detail}`);
|
|
95
|
+
lines.push(` ${r.endpoint}${r.clientVersion ? ` (client ${r.clientVersion})` : ""}`);
|
|
96
|
+
}
|
|
97
|
+
lines.push("");
|
|
98
|
+
lines.push("✗ auth_rejected / shape_changed = the client's request contract likely changed — review the headers/parser.");
|
|
99
|
+
return lines.join("\n");
|
|
100
|
+
}
|
|
101
|
+
/** `devtools cursor-key set|clear|status` — store the machine-local Cursor API key. */
|
|
102
|
+
function registerCursorKeyCommand(parent, emit) {
|
|
103
|
+
const key = parent
|
|
104
|
+
.command("cursor-key")
|
|
105
|
+
.description("Manage the Cursor API key used for the Cloud Agent enrichment");
|
|
106
|
+
key
|
|
107
|
+
.command("set [value]")
|
|
108
|
+
.description("Store a Cursor API key (reads stdin when [value] is omitted)")
|
|
109
|
+
.action(async (value) => {
|
|
110
|
+
const raw = value ?? (await readStdin());
|
|
111
|
+
const trimmed = raw.trim();
|
|
112
|
+
if (!trimmed) {
|
|
113
|
+
emit.error({ code: "empty_key", message: "no key provided (arg or stdin)" });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const path = cursorApiKeyPath();
|
|
117
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
118
|
+
writeFileSync(path, trimmed, { mode: 0o600 });
|
|
119
|
+
chmodSync(path, 0o600);
|
|
120
|
+
emit.file(path, { stored: true, length: trimmed.length });
|
|
121
|
+
});
|
|
122
|
+
key
|
|
123
|
+
.command("clear")
|
|
124
|
+
.description("Remove the stored Cursor API key")
|
|
125
|
+
.action(() => {
|
|
126
|
+
try {
|
|
127
|
+
rmSync(cursorApiKeyPath());
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// already absent
|
|
131
|
+
}
|
|
132
|
+
emit.data({ ok: true, cleared: true });
|
|
133
|
+
});
|
|
134
|
+
key
|
|
135
|
+
.command("status")
|
|
136
|
+
.description("Report whether a Cursor key is configured (env or file)")
|
|
137
|
+
.action(() => {
|
|
138
|
+
const fromEnv = Boolean(process.env.CURSOR_API_KEY?.trim());
|
|
139
|
+
const resolved = resolveCursorApiKey();
|
|
140
|
+
emit.data({
|
|
141
|
+
ok: true,
|
|
142
|
+
configured: Boolean(resolved),
|
|
143
|
+
source: fromEnv ? "env" : resolved ? "file" : null,
|
|
144
|
+
path: cursorApiKeyPath(),
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function readStdin() {
|
|
149
|
+
return new Promise((resolve) => {
|
|
150
|
+
let data = "";
|
|
151
|
+
if (process.stdin.isTTY) {
|
|
152
|
+
resolve("");
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
process.stdin.setEncoding("utf8");
|
|
156
|
+
process.stdin.on("data", (c) => {
|
|
157
|
+
data += c;
|
|
158
|
+
});
|
|
159
|
+
process.stdin.on("end", () => resolve(data));
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function collect(value, prev) {
|
|
163
|
+
return [...prev, value];
|
|
164
|
+
}
|
|
165
|
+
function renderTable(tools) {
|
|
166
|
+
const lines = [];
|
|
167
|
+
for (const t of tools) {
|
|
168
|
+
lines.push(`── ${t.tool} ${"─".repeat(Math.max(0, 40 - t.tool.length))}`);
|
|
169
|
+
if (!t.installed) {
|
|
170
|
+
lines.push(" not installed");
|
|
171
|
+
lines.push("");
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
lines.push(` logged in ${fmtBool(t.loggedIn)}`);
|
|
175
|
+
if (t.account)
|
|
176
|
+
lines.push(` account ${t.account}`);
|
|
177
|
+
if (t.plan)
|
|
178
|
+
lines.push(` plan ${t.plan}`);
|
|
179
|
+
if (t.rateLimitTier)
|
|
180
|
+
lines.push(` rate tier ${t.rateLimitTier}`);
|
|
181
|
+
if (t.authExpiresAt)
|
|
182
|
+
lines.push(` auth expires ${fmtDate(t.authExpiresAt)}`);
|
|
183
|
+
if (t.sessions != null)
|
|
184
|
+
lines.push(` sessions ${t.sessions}`);
|
|
185
|
+
if (t.lastActivity)
|
|
186
|
+
lines.push(` last active ${fmtDate(t.lastActivity)}`);
|
|
187
|
+
if (t.quota?.length) {
|
|
188
|
+
for (const q of t.quota) {
|
|
189
|
+
const pct = q.usedPercent != null ? `${q.usedPercent}% used` : "usage unknown";
|
|
190
|
+
const reset = q.resetsAt ? `resets ${fmtDate(q.resetsAt)}` : "";
|
|
191
|
+
lines.push(` quota (${q.window}) ${pct}${reset ? ` · ${reset}` : ""}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (t.tokensUsed != null) {
|
|
195
|
+
lines.push(` tokens ${t.tokensUsed.toLocaleString()}`);
|
|
196
|
+
}
|
|
197
|
+
if (t.usage) {
|
|
198
|
+
const u = t.usage;
|
|
199
|
+
if (u.cycleEnd) {
|
|
200
|
+
const days = Math.round((Date.parse(u.cycleEnd) - Date.now()) / 86_400_000);
|
|
201
|
+
lines.push(` plan resets ${fmtDate(u.cycleEnd)} (${days}d)`);
|
|
202
|
+
}
|
|
203
|
+
const pct = (label, v) => (v != null ? `${label} ${v}%` : null);
|
|
204
|
+
const bars = [
|
|
205
|
+
pct("total", u.totalPercentUsed),
|
|
206
|
+
pct("api", u.apiPercentUsed),
|
|
207
|
+
pct("first-party", u.firstPartyPercentUsed),
|
|
208
|
+
].filter((s) => s != null);
|
|
209
|
+
if (bars.length)
|
|
210
|
+
lines.push(` usage ${bars.join(" · ")}`);
|
|
211
|
+
}
|
|
212
|
+
if (t.spend?.limitCents != null) {
|
|
213
|
+
const label = t.spend.label.toLowerCase().padEnd(12).slice(0, 12);
|
|
214
|
+
lines.push(` ${label} ${fmtUsd(t.spend.usedCents ?? 0)} / ${fmtUsd(t.spend.limitCents)}`);
|
|
215
|
+
}
|
|
216
|
+
if (t.api?.ok && t.api.cloudAgents) {
|
|
217
|
+
lines.push(` cloud agents ${t.api.cloudAgents.total} (${t.api.cloudAgents.active} active)`);
|
|
218
|
+
}
|
|
219
|
+
for (const n of t.notes)
|
|
220
|
+
lines.push(` · ${n}`);
|
|
221
|
+
lines.push("");
|
|
222
|
+
}
|
|
223
|
+
return lines.join("\n").trimEnd();
|
|
224
|
+
}
|
|
225
|
+
function fmtBool(v) {
|
|
226
|
+
if (v === true)
|
|
227
|
+
return "yes";
|
|
228
|
+
if (v === false)
|
|
229
|
+
return "no";
|
|
230
|
+
return "unknown";
|
|
231
|
+
}
|
|
232
|
+
function fmtDate(iso) {
|
|
233
|
+
// Keep the machine-readable ISO but drop milliseconds for readability.
|
|
234
|
+
return iso.replace(/\.\d{3}Z$/, "Z");
|
|
235
|
+
}
|
|
236
|
+
/** Cents → "$X.XX". */
|
|
237
|
+
function fmtUsd(cents) {
|
|
238
|
+
return `$${(cents / 100).toFixed(2)}`;
|
|
239
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../src/commands/docs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../src/commands/docs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAiC1E,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,WAAW,EACtB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CA8GN"}
|
package/dist/commands/docs.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { initDocsContext as initDocs, scanDocs } from "../lib/docs.js";
|
|
2
|
+
import { initDocsMigrationContext, runFrontmatterMigration, } from "../lib/docs-frontmatter-migrate.js";
|
|
2
3
|
import { initDocsContext as initDocsIndex, runIndex } from "../lib/docs-index.js";
|
|
3
4
|
import { initDocsContext as initDocsLint, runLint } from "../lib/docs-lint.js";
|
|
5
|
+
import { readDocsMetadata, readDocsMetadataKey } from "../lib/docs-meta.js";
|
|
4
6
|
import { countColdHandoffs, initDocsContext as initDocsSweep, runSweep, } from "../lib/docs-sweep.js";
|
|
5
7
|
function ensureContext(context) {
|
|
6
8
|
if (!context?.repoRoot || !context?.submodules) {
|
|
@@ -8,6 +10,7 @@ function ensureContext(context) {
|
|
|
8
10
|
}
|
|
9
11
|
const opts = { repoRoot: context.repoRoot, submodules: context.submodules };
|
|
10
12
|
initDocs(opts);
|
|
13
|
+
initDocsMigrationContext(opts);
|
|
11
14
|
initDocsIndex(opts);
|
|
12
15
|
initDocsLint({
|
|
13
16
|
...opts,
|
|
@@ -21,7 +24,7 @@ export function registerDocsCommand(program, emitParam, context) {
|
|
|
21
24
|
emit = emitParam;
|
|
22
25
|
const docs = program
|
|
23
26
|
.command("docs")
|
|
24
|
-
.description("Documentation tooling: freshness report, lint, sweep, index")
|
|
27
|
+
.description("Documentation tooling: freshness report, metadata, lint, sweep, index")
|
|
25
28
|
// Options on the group itself back the default (no-subcommand) behavior.
|
|
26
29
|
// See handleDocs below.
|
|
27
30
|
.option("--stale <days>", "Only show files not committed in N+ days", Number.parseInt)
|
|
@@ -39,6 +42,37 @@ export function registerDocsCommand(program, emitParam, context) {
|
|
|
39
42
|
emit.error({ code: "docs_error", message: msg });
|
|
40
43
|
}
|
|
41
44
|
});
|
|
45
|
+
docs
|
|
46
|
+
.command("meta")
|
|
47
|
+
.description("Read YAML frontmatter from a documentation file")
|
|
48
|
+
.argument("<path>", "Markdown file path, relative to the project root or absolute")
|
|
49
|
+
.argument("[key]", "Optional top-level frontmatter key")
|
|
50
|
+
.option("--json", "Emit a requested key as JSON even in an interactive terminal")
|
|
51
|
+
.action(async (path, key, opts) => {
|
|
52
|
+
try {
|
|
53
|
+
ensureContext(context);
|
|
54
|
+
handleMeta(context.repoRoot, path, key, opts);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
58
|
+
emit.error({ code: "docs_error", message: msg });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
docs
|
|
62
|
+
.command("frontmatter-migrate")
|
|
63
|
+
.description("Convert lifecycle docs from bold metadata headers to YAML frontmatter")
|
|
64
|
+
.option("--repo <name>", "Limit to one submodule or '.' for parent")
|
|
65
|
+
.option("--yes", "Apply changes; without this flag the command is a dry-run")
|
|
66
|
+
.action(async (opts) => {
|
|
67
|
+
try {
|
|
68
|
+
ensureContext(context);
|
|
69
|
+
handleFrontmatterMigration(opts);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
73
|
+
emit.error({ code: "docs_error", message: msg });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
42
76
|
docs
|
|
43
77
|
.command("lint")
|
|
44
78
|
.description("Verify every repo matches the documentation contract (directory layout + naming rules)")
|
|
@@ -86,6 +120,40 @@ export function registerDocsCommand(program, emitParam, context) {
|
|
|
86
120
|
}
|
|
87
121
|
});
|
|
88
122
|
}
|
|
123
|
+
// --- `harn docs frontmatter-migrate` ---
|
|
124
|
+
function handleFrontmatterMigration(opts) {
|
|
125
|
+
const rows = runFrontmatterMigration({ repo: opts.repo, apply: !!opts.yes });
|
|
126
|
+
const counts = {
|
|
127
|
+
would_update: rows.filter((row) => row.status === "would-update").length,
|
|
128
|
+
updated: rows.filter((row) => row.status === "updated").length,
|
|
129
|
+
skipped: rows.filter((row) => row.status === "skipped").length,
|
|
130
|
+
errors: rows.filter((row) => row.status === "error").length,
|
|
131
|
+
};
|
|
132
|
+
emit.data({
|
|
133
|
+
dry_run: !opts.yes,
|
|
134
|
+
applied: !!opts.yes && counts.errors === 0,
|
|
135
|
+
aborted: !!opts.yes && counts.errors > 0,
|
|
136
|
+
repo: opts.repo ?? null,
|
|
137
|
+
counts,
|
|
138
|
+
rows,
|
|
139
|
+
});
|
|
140
|
+
if (counts.errors > 0)
|
|
141
|
+
emit.setExitCode(1);
|
|
142
|
+
}
|
|
143
|
+
// --- `harn docs meta` ---
|
|
144
|
+
function handleMeta(repoRoot, path, key, opts) {
|
|
145
|
+
const metadata = readDocsMetadata(repoRoot, path).data;
|
|
146
|
+
if (!key) {
|
|
147
|
+
emit.data(metadata);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const value = readDocsMetadataKey(metadata, key, path);
|
|
151
|
+
if (opts.json || !process.stdout.isTTY || typeof value === "object") {
|
|
152
|
+
emit.data(value);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
emit.text(String(value));
|
|
156
|
+
}
|
|
89
157
|
// --- Default `harn docs` (freshness report) ---
|
|
90
158
|
async function handleDocs(opts) {
|
|
91
159
|
if (opts.format === "json")
|
package/dist/commands/doctor.js
CHANGED
|
@@ -220,17 +220,25 @@ function checkHarnessHooks() {
|
|
|
220
220
|
const bin = resolveBinName(root);
|
|
221
221
|
const parts = drift.map((d) => {
|
|
222
222
|
const bits = [];
|
|
223
|
+
if (d.parseError)
|
|
224
|
+
bits.push(`invalid JSON (${d.parseError})`);
|
|
223
225
|
if (d.missing.length > 0) {
|
|
224
226
|
bits.push(`${d.missing.length} missing (${d.missing.map((m) => m.subcommand).join(", ")})`);
|
|
225
227
|
}
|
|
226
228
|
if (d.orphans.length > 0)
|
|
227
229
|
bits.push(`${d.orphans.length} orphaned (${d.orphans.join(", ")})`);
|
|
230
|
+
if (d.invalidTopLevelKeys.length > 0) {
|
|
231
|
+
bits.push(`invalid fields (${d.invalidTopLevelKeys.join(", ")})`);
|
|
232
|
+
}
|
|
233
|
+
if (d.invalidEventKeys.length > 0) {
|
|
234
|
+
bits.push(`unsupported events (${d.invalidEventKeys.join(", ")})`);
|
|
235
|
+
}
|
|
228
236
|
return `${d.settingsFile}: ${bits.join("; ")}`;
|
|
229
237
|
});
|
|
230
|
-
const
|
|
231
|
-
const hint =
|
|
232
|
-
? `
|
|
233
|
-
: `run \`${bin} init\` to
|
|
238
|
+
const needsManualRepair = drift.some((d) => d.parseError || d.invalidTopLevelKeys.length > 0 || d.invalidEventKeys.length > 0);
|
|
239
|
+
const hint = needsManualRepair
|
|
240
|
+
? `repair the invalid harness settings, then run \`${bin} init\` to migrate harnery hooks`
|
|
241
|
+
: `run \`${bin} init\` to migrate the hook set (idempotent)`;
|
|
234
242
|
return { name: "harness hooks", severity: "warn", detail: parts.join(" | "), hint };
|
|
235
243
|
}
|
|
236
244
|
function macOrLinux(mac, linux) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/commands/env.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/commands/env.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAwB,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAqBhG,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CAaN"}
|