hafez 1.4.0 → 2.0.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.
Files changed (63) hide show
  1. package/README.md +26 -33
  2. package/dist/apply.d.ts +5 -2
  3. package/dist/apply.js +225 -114
  4. package/dist/batch-ops.d.ts +89 -26
  5. package/dist/batch-ops.js +176 -18
  6. package/dist/cli/args.d.ts +43 -5
  7. package/dist/cli/args.js +69 -8
  8. package/dist/cli/batch-errors.js +19 -11
  9. package/dist/cli/block-hook.d.ts +9 -0
  10. package/dist/cli/block-hook.js +90 -0
  11. package/dist/cli/commands-schema.js +2 -0
  12. package/dist/cli/commands.d.ts +4 -3
  13. package/dist/cli/commands.js +300 -99
  14. package/dist/cli/format.d.ts +7 -3
  15. package/dist/cli/format.js +27 -10
  16. package/dist/cli/index.d.ts +18 -4
  17. package/dist/cli/index.js +72 -30
  18. package/dist/cli/install.d.ts +53 -0
  19. package/dist/cli/install.js +297 -0
  20. package/dist/cli/onboard.d.ts +15 -2
  21. package/dist/cli/onboard.js +265 -90
  22. package/dist/cli/resolve-vault.d.ts +11 -4
  23. package/dist/cli/resolve-vault.js +13 -11
  24. package/dist/cli/schema-introspect.js +4 -3
  25. package/dist/cli/upgrade.d.ts +13 -4
  26. package/dist/cli/upgrade.js +90 -44
  27. package/dist/closest.d.ts +8 -0
  28. package/dist/closest.js +65 -0
  29. package/dist/contracts.d.ts +17 -1
  30. package/dist/contracts.js +44 -0
  31. package/dist/db.d.ts +15 -2
  32. package/dist/db.js +69 -48
  33. package/dist/digest.d.ts +6 -4
  34. package/dist/digest.js +28 -17
  35. package/dist/document.d.ts +41 -3
  36. package/dist/document.js +218 -62
  37. package/dist/export-okf.d.ts +13 -0
  38. package/dist/export-okf.js +11 -7
  39. package/dist/harness.d.ts +58 -0
  40. package/dist/harness.js +50 -0
  41. package/dist/index.d.ts +1 -1
  42. package/dist/index.js +94 -104
  43. package/dist/merge.js +7 -5
  44. package/dist/near-match.d.ts +9 -0
  45. package/dist/near-match.js +34 -0
  46. package/dist/stale.d.ts +41 -0
  47. package/dist/stale.js +43 -0
  48. package/dist/stopwords.d.ts +6 -0
  49. package/dist/stopwords.js +25 -0
  50. package/dist/surfaces/items.d.ts +366 -0
  51. package/dist/surfaces/items.js +523 -0
  52. package/dist/surfaces/surfaces.d.ts +32 -0
  53. package/dist/surfaces/surfaces.js +230 -0
  54. package/dist/types.d.ts +45 -16
  55. package/dist/validate.d.ts +7 -0
  56. package/dist/validate.js +109 -0
  57. package/dist/vault.js +1 -1
  58. package/package.json +4 -3
  59. package/skill/hafez/SKILL.md +68 -0
  60. package/skill/hafez-save/SKILL.md +29 -0
  61. package/dist/cli/help-agent.d.ts +0 -1
  62. package/dist/cli/help-agent.js +0 -210
  63. package/skill/SKILL.md +0 -137
@@ -1,4 +1,6 @@
1
- export function formatEntityHeader(fm, body) {
1
+ /** `parent` is the rendered value of the `parent:` line (the slug with the
2
+ * parent's description); absent, the slug alone. */
3
+ export function formatEntityHeader(fm, body, parent = fm.parent) {
2
4
  const lines = [`# ${fm.name}`];
3
5
  if (fm.description)
4
6
  lines.push(`> ${fm.description}`);
@@ -11,9 +13,10 @@ export function formatEntityHeader(fm, body) {
11
13
  meta.push(`domain: ${fm.domain.join(', ')}`);
12
14
  if (meta.length)
13
15
  lines.push(meta.join(' | '));
16
+ // The parent line stands alone: with its description it is a sentence.
17
+ if (parent)
18
+ lines.push(`parent: ${parent}`);
14
19
  const meta2 = [];
15
- if (fm.parent)
16
- meta2.push(`parent: ${fm.parent}`);
17
20
  if (fm['last-touched'])
18
21
  meta2.push(`last-touched: ${fm['last-touched']}`);
19
22
  if (fm.created)
@@ -34,18 +37,33 @@ export function formatKnowledgeHeader(fm, body) {
34
37
  const lines = [`# ${fm.name}`];
35
38
  if (fm.description)
36
39
  lines.push(`> ${fm.description}`);
40
+ // A plan has a lifecycle and no reinforcement: its subtype, status and
41
+ // dates; an insight its confidence and reinforcement.
42
+ const plan = fm.subtype === 'plan';
37
43
  const meta = [];
38
- if (fm.confidence)
44
+ if (plan)
45
+ meta.push('subtype: plan');
46
+ if (plan && fm.status)
47
+ meta.push(`status: ${fm.status}`);
48
+ if (!plan && fm.confidence)
39
49
  meta.push(`confidence: ${fm.confidence}`);
40
50
  if (fm.domain?.length)
41
51
  meta.push(`domain: ${Array.isArray(fm.domain) ? fm.domain.join(', ') : fm.domain}`);
42
52
  if (meta.length)
43
53
  lines.push(meta.join(' | '));
44
54
  const meta2 = [];
45
- if (fm['reinforcement-count'] != null)
46
- meta2.push(`reinforcement-count: ${fm['reinforcement-count']}`);
47
- if (fm['last-reinforced'])
48
- meta2.push(`last-reinforced: ${fm['last-reinforced']}`);
55
+ if (plan) {
56
+ if (fm.created)
57
+ meta2.push(`created: ${fm.created}`);
58
+ if (fm['last-touched'])
59
+ meta2.push(`last-touched: ${fm['last-touched']}`);
60
+ }
61
+ else {
62
+ if (fm['reinforcement-count'] != null)
63
+ meta2.push(`reinforcement-count: ${fm['reinforcement-count']}`);
64
+ if (fm['last-reinforced'])
65
+ meta2.push(`last-reinforced: ${fm['last-reinforced']}`);
66
+ }
49
67
  if (meta2.length)
50
68
  lines.push(meta2.join(' | '));
51
69
  if (fm.related?.length)
@@ -143,7 +161,7 @@ export function formatChangelog(entries) {
143
161
  }
144
162
  export function formatValidation(report) {
145
163
  const issues = report.broken_slugs.length + report.orphaned_knowledge.length +
146
- report.oversized_related.length + report.missing_fields.length;
164
+ report.missing_fields.length;
147
165
  if (issues === 0) {
148
166
  return `Vault OK — ${report.total_entities} entities, ${report.total_knowledge} knowledge, 0 issues`;
149
167
  }
@@ -157,7 +175,6 @@ export function formatValidation(report) {
157
175
  };
158
176
  addSection('Broken Slugs', report.broken_slugs.map(i => `${i.slug}: ${i.issue}`));
159
177
  addSection('Orphaned Knowledge', report.orphaned_knowledge.map(s => `${s} (no related links, not referenced by any entity)`));
160
- addSection('Oversized Related', report.oversized_related.map(i => `${i.slug}: ${i.issue}`));
161
178
  addSection('Missing Fields', report.missing_fields.map(i => `${i.slug}: ${i.issue}`));
162
179
  return sections.join('\n');
163
180
  }
@@ -1,3 +1,5 @@
1
+ import { HafezError, type HafezErrorCode } from '../types.js';
2
+ import { UsageError } from './args.js';
1
3
  import type { CommandOpts } from './commands.js';
2
4
  type PreVaultRun = (args: string[], ctx: {
3
5
  json: boolean;
@@ -13,15 +15,24 @@ export type CommandSpec = {
13
15
  run: VaultRun;
14
16
  };
15
17
  export declare const COMMANDS: Record<string, CommandSpec>;
16
- /** Exported so a guard test can assert every registered command is documented
17
- * here — a registered-but-undocumented command is otherwise invisible. */
18
- export declare const USAGE = "Usage: hafez <command> [args]\n\nCommands:\n read <slug> Read entity or knowledge note\n query [--filter ...] ... Filter entities/knowledge\n --kind all Both kinds; --filter matches entity status only,\n so knowledge notes (which have none) always show\n search <terms> Full-text search\n create <kind> <name> Create entity or knowledge note\n capture <name> [--notes] Quick inbox capture\n promote <slug> <target> Promote capture\u2192entity/project/knowledge, entity\u2192project\n update <slug> [--status] Update an entity or knowledge note\n --brief \"text\" Set/replace Brief (use --brief \"\" to clear)\n --description \"text\" Set one-line description (use --description \"\" to clear)\n --resource \"uri\" Set canonical resource URI, entities only (use --resource \"\" to clear)\n --add-action \"text\" Add a next action\n --complete-action \"text\" Complete matching action\n --remove-action \"text\" Remove matching action\n --clear-actions Remove all next actions\n --synthesis \"text\" Set/replace Synthesis section (--insight is an alias)\n --add-evidence \"text\" Append to Evidence section\n --add-source \"text\" Append to Sources section\n batch Apply multiple operations atomically (JSON on stdin)\n digest Apply digest input (JSON stdin) \u2192 batch payload (JSON stdout)\n export --okf [--out <dir>] Export vault as an OKF v0.1 bundle (read-only)\n sync Pull remote changes and push local commits\n link <slug> <target> --relation <rel> Add a link\n unlink <slug> <target> --relation <rel> Remove a link\n stats Vault summary: counts, stale items, recents\n changelog --since <when> Git-derived change history (e.g. --since 7.days.ago)\n schema [op] [--examples] Machine-readable JSON schema for batch operations\n validate Check vault integrity\n index rebuild Rebuild the SQLite index\n init --register <path> Register an existing vault\n init Show current vault resolution\n onboard Agent-directed first run: seed the vault, pick integration level\n upgrade Check whether hafez is outdated and print the update steps\n help --agent Full API reference for agents\n\nGlobal flags:\n --vault <path> Vault path (overrides config)\n --json JSON output (structured, machine-readable)\n --version Print the hafez version\n\nVault discovery: --vault flag > ~/.config/hafez/vault\n";
19
- /** Exit codes, documented in `hafez help --agent`. 0 = success; 4 is the
18
+ /** The one exit-code table. The error → exit adapter maps HafezError codes
19
+ * through `errorCodes`; `hafez --help` prints every row. 4 is the
20
20
  * mutate-succeeded-sync-failed warning path; anything unmapped = 1. */
21
+ export declare const EXIT_TABLE: ReadonlyArray<{
22
+ exit: number;
23
+ meaning: string;
24
+ errorCodes?: readonly HafezErrorCode[];
25
+ }>;
21
26
  export declare const EXIT_CODES: Record<string, number>;
27
+ /** Exported so a guard test can assert every registered command is documented
28
+ * here — a registered-but-undocumented command is otherwise invisible. */
29
+ export declare const USAGE: string;
22
30
  /** The pure half of the error → exit adapter: which code an error maps to.
23
31
  * Exported so the exit-code contract is testable without spawning the CLI. */
24
32
  export declare function exitCodeFor(err: unknown): number;
33
+ /** The text half of the error adapter: what it prints for a usage or vault
34
+ * error. A misuse ends with the shape to retry from. */
35
+ export declare function errorText(err: UsageError | HafezError): string;
25
36
  /** Does this error mean the native module failed to load/build?
26
37
  *
27
38
  * Matched on the MESSAGE only, and only on the messages a load/ABI failure
@@ -38,5 +49,8 @@ export declare function extractGlobalFlags(args: string[]): {
38
49
  vaultFlag?: string;
39
50
  json: boolean;
40
51
  };
52
+ /** Is this the SessionStart hook's form, `onboard --block`? Read from the raw
53
+ * args, the token after `--vault` skipped, so it holds when parsing them fails. */
54
+ export declare function isBlockHook(args: string[]): boolean;
41
55
  export declare function main(argv: string[]): Promise<void>;
42
56
  export {};
package/dist/cli/index.js CHANGED
@@ -10,7 +10,7 @@ import { readFileSync } from 'fs';
10
10
  import { dirname, join } from 'path';
11
11
  import { fileURLToPath } from 'url';
12
12
  import { HafezError } from '../types.js';
13
- import { UsageError } from './args.js';
13
+ import { HelpRequest, MisuseError, RenderedError, UsageError, helpIfAsked } from './args.js';
14
14
  /** Bind a vault command to its handler without importing the handler module
15
15
  * until the command actually runs. */
16
16
  function lazy(pick) {
@@ -18,8 +18,8 @@ function lazy(pick) {
18
18
  }
19
19
  export const COMMANDS = {
20
20
  // --- pre-vault ---
21
- help: { phase: 'pre-vault', run: async (args) => (args[0] === '--agent' ? (await import('./help-agent.js')).renderAgentHelp() : USAGE) },
22
- onboard: { phase: 'pre-vault', run: async () => (await import('./onboard.js')).renderOnboard() },
21
+ help: { phase: 'pre-vault', run: async (args) => (args[0] === '--agent' ? (await import('../surfaces/surfaces.js')).renderAgentHelp() : USAGE) },
22
+ onboard: { phase: 'pre-vault', run: async (args, ctx) => (await import('./onboard.js')).cmdOnboard(args, { vaultFlag: ctx.vaultFlag }) },
23
23
  schema: { phase: 'pre-vault', run: async (args) => (await import('./commands-schema.js')).cmdSchema(null, args) },
24
24
  version: { phase: 'pre-vault', run: () => versionLine() },
25
25
  init: { phase: 'pre-vault', run: async (args, ctx) => { (await import('./resolve-vault.js')).cmdInit(args, ctx.vaultFlag); return null; } },
@@ -38,14 +38,17 @@ export const COMMANDS = {
38
38
  link: { phase: 'vault', mutating: true, run: lazy(m => m.cmdLink) },
39
39
  unlink: { phase: 'vault', mutating: true, run: lazy(m => m.cmdUnlink) },
40
40
  promote: { phase: 'vault', mutating: true, run: lazy(m => m.cmdPromote) },
41
+ delete: { phase: 'vault', mutating: true, run: lazy(m => m.cmdDelete) },
41
42
  batch: { phase: 'vault', mutating: true, run: lazy(m => m.cmdBatch) },
42
43
  validate: { phase: 'vault', run: lazy(m => m.cmdValidate) },
43
44
  index: {
44
45
  phase: 'vault',
45
46
  run: async (os, args) => {
47
+ const usage = 'Usage: hafez index rebuild';
48
+ helpIfAsked(args, usage);
46
49
  if (args[0] === 'rebuild')
47
50
  return (await import('./commands.js')).cmdIndexRebuild(os);
48
- throw new UsageError('Usage: hafez index rebuild');
51
+ throw new UsageError(usage);
49
52
  },
50
53
  },
51
54
  stats: { phase: 'vault', run: lazy(m => m.cmdStats) },
@@ -53,6 +56,20 @@ export const COMMANDS = {
53
56
  digest: { phase: 'vault', run: lazy(m => m.cmdDigest) },
54
57
  export: { phase: 'vault', run: lazy(m => m.cmdExport) },
55
58
  };
59
+ /** The one exit-code table. The error → exit adapter maps HafezError codes
60
+ * through `errorCodes`; `hafez --help` prints every row. 4 is the
61
+ * mutate-succeeded-sync-failed warning path; anything unmapped = 1. */
62
+ export const EXIT_TABLE = [
63
+ { exit: 0, meaning: 'success' },
64
+ { exit: 1, meaning: 'not found (slug, command or vault) / unknown error', errorCodes: ['NOT_FOUND'] },
65
+ { exit: 2, meaning: 'validation or usage error (bad flags, bad payload, core rejected the write)', errorCodes: ['VALIDATION_FAILED'] },
66
+ { exit: 3, meaning: 'git push failed — the write IS committed locally', errorCodes: ['GIT_PUSH_FAILED'] },
67
+ { exit: 4, meaning: 'write succeeded but the auto-sync after it failed' },
68
+ { exit: 5, meaning: 'slug already exists', errorCodes: ['SLUG_EXISTS'] },
69
+ { exit: 6, meaning: 'git commit failed — nothing was written to history', errorCodes: ['GIT_COMMIT_FAILED'] },
70
+ { exit: 7, meaning: 'vault locked by another hafez process — retry when it finishes', errorCodes: ['VAULT_LOCKED'] },
71
+ ];
72
+ export const EXIT_CODES = Object.fromEntries(EXIT_TABLE.flatMap(row => (row.errorCodes ?? []).map(code => [code, row.exit])));
56
73
  /** Exported so a guard test can assert every registered command is documented
57
74
  * here — a registered-but-undocumented command is otherwise invisible. */
58
75
  export const USAGE = `Usage: hafez <command> [args]
@@ -66,19 +83,20 @@ Commands:
66
83
  create <kind> <name> Create entity or knowledge note
67
84
  capture <name> [--notes] Quick inbox capture
68
85
  promote <slug> <target> Promote capture→entity/project/knowledge, entity→project
86
+ delete <slug> Delete a knowledge or session note (an entity: update --status done)
69
87
  update <slug> [--status] Update an entity or knowledge note
70
88
  --brief "text" Set/replace Brief (use --brief "" to clear)
71
89
  --description "text" Set one-line description (use --description "" to clear)
72
90
  --resource "uri" Set canonical resource URI, entities only (use --resource "" to clear)
73
91
  --add-action "text" Add a next action
74
- --complete-action "text" Complete matching action
75
- --remove-action "text" Remove matching action
92
+ --complete-action handle-or-text Complete the action with that Handle (as read shows it), else the matching one
93
+ --remove-action handle-or-text Remove the action with that Handle, else the matching one
76
94
  --clear-actions Remove all next actions
77
95
  --synthesis "text" Set/replace Synthesis section (--insight is an alias)
78
96
  --add-evidence "text" Append to Evidence section
79
97
  --add-source "text" Append to Sources section
80
- batch Apply multiple operations atomically (JSON on stdin)
81
- digest Apply digest input (JSON stdin) → batch payload (JSON stdout)
98
+ batch Apply multiple operations atomically (JSON on stdin, --data or --file)
99
+ digest Apply digest input (JSON on stdin, --data or --file) → batch payload (JSON stdout)
82
100
  export --okf [--out <dir>] Export vault as an OKF v0.1 bundle (read-only)
83
101
  sync Pull remote changes and push local commits
84
102
  link <slug> <target> --relation <rel> Add a link
@@ -90,9 +108,14 @@ Commands:
90
108
  index rebuild Rebuild the SQLite index
91
109
  init --register <path> Register an existing vault
92
110
  init Show current vault resolution
93
- onboard Agent-directed first run: seed the vault, pick integration level
111
+ onboard Setup status, then the script your agent follows to set Hafez up
112
+ --install --mode <always-on|invoke-only> [--harness <h>] [--confirm]
113
+ Write both skills and, always-on, the Block
114
+ --remove [--confirm] Remove the skills and the Block region
115
+ --block [--harness <h>] Print the Block and the entities matching this folder
116
+ (the SessionStart hook; prints nothing on any failure)
94
117
  upgrade Check whether hafez is outdated and print the update steps
95
- help --agent Full API reference for agents
118
+ help --agent The hafez skill body: the reference when the skill is not installed
96
119
 
97
120
  Global flags:
98
121
  --vault <path> Vault path (overrides config)
@@ -100,39 +123,45 @@ Global flags:
100
123
  --version Print the hafez version
101
124
 
102
125
  Vault discovery: --vault flag > ~/.config/hafez/vault
126
+
127
+ Exit codes:
128
+ ${EXIT_TABLE.map(row => ` ${row.exit} ${row.meaning}`).join('\n')}
103
129
  `;
104
- /** Exit codes, documented in `hafez help --agent`. 0 = success; 4 is the
105
- * mutate-succeeded-sync-failed warning path; anything unmapped = 1. */
106
- export const EXIT_CODES = {
107
- NOT_FOUND: 1,
108
- VALIDATION_FAILED: 2,
109
- GIT_PUSH_FAILED: 3,
110
- SLUG_EXISTS: 5,
111
- GIT_COMMIT_FAILED: 6,
112
- VAULT_LOCKED: 7,
113
- };
114
130
  /** The pure half of the error → exit adapter: which code an error maps to.
115
131
  * Exported so the exit-code contract is testable without spawning the CLI. */
116
132
  export function exitCodeFor(err) {
133
+ if (err instanceof HelpRequest)
134
+ return 0;
117
135
  if (err instanceof UsageError)
118
136
  return 2;
119
137
  if (err instanceof HafezError)
120
138
  return EXIT_CODES[err.code] ?? 1;
121
139
  return 1;
122
140
  }
141
+ /** The text half of the error adapter: what it prints for a usage or vault
142
+ * error. A misuse ends with the shape to retry from. */
143
+ export function errorText(err) {
144
+ const lines = [`Error: ${err.message}`];
145
+ if (err instanceof HafezError)
146
+ for (const detail of err.details ?? [])
147
+ lines.push(` - ${detail}`);
148
+ if (err instanceof MisuseError)
149
+ lines.push('', err.shape);
150
+ return lines.join('\n') + '\n';
151
+ }
123
152
  /** The one error → exit adapter. Usage errors (bad flags/args) are distinct
124
153
  * from vault errors and from unknown crashes. */
125
154
  function report(err) {
126
- if (err instanceof UsageError) {
127
- process.stderr.write(`Error: ${err.message}\n`);
155
+ if (err instanceof HelpRequest) {
156
+ process.stdout.write(err.usage + '\n');
128
157
  process.exit(exitCodeFor(err));
129
158
  }
130
- if (err instanceof HafezError) {
131
- process.stderr.write(`Error: ${err.message}\n`);
132
- if (err.details?.length) {
133
- for (const detail of err.details)
134
- process.stderr.write(` - ${detail}\n`);
135
- }
159
+ if (err instanceof RenderedError) {
160
+ process[err.stream].write(err.output + '\n');
161
+ process.exit(exitCodeFor(err));
162
+ }
163
+ if (err instanceof UsageError || err instanceof HafezError) {
164
+ process.stderr.write(errorText(err));
136
165
  process.exit(exitCodeFor(err));
137
166
  }
138
167
  failUnknown(err);
@@ -202,7 +231,16 @@ export function extractGlobalFlags(args) {
202
231
  }
203
232
  return { rest, vaultFlag, json };
204
233
  }
234
+ /** Is this the SessionStart hook's form, `onboard --block`? Read from the raw
235
+ * args, the token after `--vault` skipped, so it holds when parsing them fails. */
236
+ export function isBlockHook(args) {
237
+ const command = args.find((a, i) => !a.startsWith('-') && args[i - 1] !== '--vault');
238
+ return command === 'onboard' && args.includes('--block');
239
+ }
205
240
  export async function main(argv) {
241
+ // The hook must never block a session: under `onboard --block` any failure
242
+ // at all, a bad flag included, prints nothing and exits 0 (#132).
243
+ const hook = isBlockHook(argv.slice(2));
206
244
  try {
207
245
  const { rest: args, vaultFlag, json } = extractGlobalFlags(argv.slice(2));
208
246
  const command = args.shift();
@@ -211,9 +249,11 @@ export async function main(argv) {
211
249
  return;
212
250
  }
213
251
  const name = command === '--version' || command === '-v' ? 'version' : command;
214
- const spec = COMMANDS[name];
252
+ const spec = Object.hasOwn(COMMANDS, name) ? COMMANDS[name] : undefined;
215
253
  if (!spec) {
216
- process.stderr.write(`Unknown command: ${command}\n${USAGE}`);
254
+ // `list` is the command agents guess for listing the vault (#30).
255
+ const hint = command === 'list' ? '. To list the vault: hafez query --filter active' : '';
256
+ process.stderr.write(`Unknown command: ${command}${hint}\n${USAGE}`);
217
257
  process.exit(1);
218
258
  }
219
259
  if (spec.phase === 'pre-vault') {
@@ -256,6 +296,8 @@ export async function main(argv) {
256
296
  process.stdout.write(result + '\n');
257
297
  }
258
298
  catch (err) {
299
+ if (hook)
300
+ return;
259
301
  report(err);
260
302
  }
261
303
  }
@@ -0,0 +1,53 @@
1
+ import { type HarnessId } from '../harness.js';
2
+ export declare const MODES: readonly ["always-on", "invoke-only"];
3
+ export type Mode = (typeof MODES)[number];
4
+ export declare const isMode: (value: string) => value is Mode;
5
+ export declare const SKILL_DIRS: string[];
6
+ export type HashState = 'absent' | 'current' | 'stale' | 'hand-edited';
7
+ /** What to do about broken markers: a hand edit, then `rerun`. */
8
+ export declare const fixBrokenMarkers: (path: string, rerun: string) => string;
9
+ /** Codex reads an `AGENTS.override.md` in place of `AGENTS.md` when one
10
+ * exists: the first candidate present, else the last. */
11
+ export declare const instructionsFile: (home: string, candidates: readonly string[]) => string;
12
+ export interface InstallState {
13
+ /** In the Harness's instructions file; null where it keeps no file.
14
+ * `broken`: markers `--install` and `--remove` refuse until fixed by hand. */
15
+ block: HashState | 'broken' | null;
16
+ /** Across both skill directories: the one state every file shares, else
17
+ * hand-edited when any file is, else stale. */
18
+ skills: HashState;
19
+ /** Some skill file carries a hash: `onboard --install` has run here. */
20
+ onboarded: boolean;
21
+ }
22
+ export declare function installState(home: string, harness: HarnessId): InstallState;
23
+ export declare function install(opts: {
24
+ home: string;
25
+ harness: HarnessId;
26
+ mode: Mode;
27
+ confirm: boolean;
28
+ }): string;
29
+ export interface SkillCheck {
30
+ path: string;
31
+ state: HashState;
32
+ }
33
+ export interface BlockCheck {
34
+ harness: HarnessId;
35
+ path: string;
36
+ /** `broken`: markers `--install` would refuse until fixed by hand. */
37
+ state: Exclude<HashState, 'absent'> | 'broken';
38
+ /** The Block Hafez writes has changed since this one was written. */
39
+ renderChanged: boolean;
40
+ }
41
+ /** Both skills in both directories, and the Block in the instructions file
42
+ * each Harness reads — the one `--install` would write. Only Blocks that are
43
+ * there are listed: none means invoke-only. */
44
+ export declare function checkInstall(home: string): {
45
+ skills: SkillCheck[];
46
+ blocks: BlockCheck[];
47
+ };
48
+ /** Remove everything Hafez wrote: both skills from both directories, and the
49
+ * Block region from every instructions file a Harness reads. Nothing else. */
50
+ export declare function remove({ home, confirm }: {
51
+ home: string;
52
+ confirm: boolean;
53
+ }): string;