@groundnuty/macf 0.2.37 → 0.2.39

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 (82) hide show
  1. package/dist/.build-info.json +2 -2
  2. package/dist/cli/claude-sh.d.ts.map +1 -1
  3. package/dist/cli/claude-sh.js +13 -0
  4. package/dist/cli/claude-sh.js.map +1 -1
  5. package/dist/cli/commands/certs.d.ts.map +1 -1
  6. package/dist/cli/commands/certs.js +6 -2
  7. package/dist/cli/commands/certs.js.map +1 -1
  8. package/dist/cli/commands/doctor.d.ts +102 -3
  9. package/dist/cli/commands/doctor.d.ts.map +1 -1
  10. package/dist/cli/commands/doctor.js +349 -55
  11. package/dist/cli/commands/doctor.js.map +1 -1
  12. package/dist/cli/commands/fleet-doctor-inject.d.ts +52 -0
  13. package/dist/cli/commands/fleet-doctor-inject.d.ts.map +1 -0
  14. package/dist/cli/commands/fleet-doctor-inject.js +100 -0
  15. package/dist/cli/commands/fleet-doctor-inject.js.map +1 -0
  16. package/dist/cli/commands/fleet-doctor.d.ts +236 -0
  17. package/dist/cli/commands/fleet-doctor.d.ts.map +1 -0
  18. package/dist/cli/commands/fleet-doctor.js +481 -0
  19. package/dist/cli/commands/fleet-doctor.js.map +1 -0
  20. package/dist/cli/commands/fleet.d.ts +83 -0
  21. package/dist/cli/commands/fleet.d.ts.map +1 -0
  22. package/dist/cli/commands/fleet.js +225 -0
  23. package/dist/cli/commands/fleet.js.map +1 -0
  24. package/dist/cli/commands/init.d.ts +24 -0
  25. package/dist/cli/commands/init.d.ts.map +1 -1
  26. package/dist/cli/commands/init.js +79 -8
  27. package/dist/cli/commands/init.js.map +1 -1
  28. package/dist/cli/commands/migrate.d.ts +1 -0
  29. package/dist/cli/commands/migrate.d.ts.map +1 -1
  30. package/dist/cli/commands/ps.d.ts +17 -0
  31. package/dist/cli/commands/ps.d.ts.map +1 -0
  32. package/dist/cli/commands/ps.js +69 -0
  33. package/dist/cli/commands/ps.js.map +1 -0
  34. package/dist/cli/commands/registry-prune.d.ts +81 -0
  35. package/dist/cli/commands/registry-prune.d.ts.map +1 -0
  36. package/dist/cli/commands/registry-prune.js +163 -0
  37. package/dist/cli/commands/registry-prune.js.map +1 -0
  38. package/dist/cli/commands/restart-self.d.ts +111 -0
  39. package/dist/cli/commands/restart-self.d.ts.map +1 -0
  40. package/dist/cli/commands/restart-self.js +312 -0
  41. package/dist/cli/commands/restart-self.js.map +1 -0
  42. package/dist/cli/commands/routing-doctor-gh.d.ts +29 -0
  43. package/dist/cli/commands/routing-doctor-gh.d.ts.map +1 -0
  44. package/dist/cli/commands/routing-doctor-gh.js +103 -0
  45. package/dist/cli/commands/routing-doctor-gh.js.map +1 -0
  46. package/dist/cli/commands/routing-doctor.d.ts +183 -0
  47. package/dist/cli/commands/routing-doctor.d.ts.map +1 -0
  48. package/dist/cli/commands/routing-doctor.js +504 -0
  49. package/dist/cli/commands/routing-doctor.js.map +1 -0
  50. package/dist/cli/commands/update.d.ts.map +1 -1
  51. package/dist/cli/commands/update.js +9 -0
  52. package/dist/cli/commands/update.js.map +1 -1
  53. package/dist/cli/config.d.ts +2 -0
  54. package/dist/cli/config.d.ts.map +1 -1
  55. package/dist/cli/config.js +16 -0
  56. package/dist/cli/config.js.map +1 -1
  57. package/dist/cli/env-files.d.ts.map +1 -1
  58. package/dist/cli/env-files.js +11 -0
  59. package/dist/cli/env-files.js.map +1 -1
  60. package/dist/cli/host-prelude.d.ts +50 -0
  61. package/dist/cli/host-prelude.d.ts.map +1 -0
  62. package/dist/cli/host-prelude.js +256 -0
  63. package/dist/cli/host-prelude.js.map +1 -0
  64. package/dist/cli/index.js +122 -5
  65. package/dist/cli/index.js.map +1 -1
  66. package/dist/cli/proc-scan.d.ts +81 -0
  67. package/dist/cli/proc-scan.d.ts.map +1 -0
  68. package/dist/cli/proc-scan.js +172 -0
  69. package/dist/cli/proc-scan.js.map +1 -0
  70. package/dist/cli/role-settings-model.d.ts +70 -0
  71. package/dist/cli/role-settings-model.d.ts.map +1 -0
  72. package/dist/cli/role-settings-model.js +90 -0
  73. package/dist/cli/role-settings-model.js.map +1 -0
  74. package/dist/cli/settings-writer.d.ts +27 -0
  75. package/dist/cli/settings-writer.d.ts.map +1 -1
  76. package/dist/cli/settings-writer.js +144 -2
  77. package/dist/cli/settings-writer.js.map +1 -1
  78. package/package.json +2 -2
  79. package/plugin/rules/coordination.md +10 -0
  80. package/plugin/rules/silent-fallback-hazards.md +19 -4
  81. package/scripts/check-gh-attribution.sh +34 -10
  82. package/scripts/emit-turn-receipt.sh +44 -4
@@ -0,0 +1,81 @@
1
+ import type { AgentInfo, HealthResponse } from '@groundnuty/macf-core';
2
+ /** A registry entry's liveness verdict. `alive` = keep, `dead` = remove. */
3
+ export type PruneVerdict = 'alive' | 'dead';
4
+ /**
5
+ * Why an entry got its verdict — surfaced in the report so the operator sees the
6
+ * basis before consenting (DR-031, groundnuty/macf#568):
7
+ * - `'responding'` — a /health probe answered → keep (a successful probe
8
+ * ALWAYS means alive, regardless of heartbeat state).
9
+ * - `'recent-heartbeat'`— probes failed BUT `last_heartbeat` is fresh (< TTL) →
10
+ * keep; the instance beat within the TTL, so an
11
+ * unreachable /health is treated as a transient blip.
12
+ * - `'stale-heartbeat'` — probes failed AND `last_heartbeat` aged past the TTL →
13
+ * remove; the stale beat reinforces the dead verdict.
14
+ * - `'unreachable'` — probes failed AND there is no heartbeat data (pre-DR-031
15
+ * or heartbeat-disabled) → remove (the legacy verdict).
16
+ */
17
+ export type PruneReason = 'responding' | 'recent-heartbeat' | 'stale-heartbeat' | 'unreachable';
18
+ /** One classified registry entry. */
19
+ export interface PruneEntry {
20
+ readonly name: string;
21
+ readonly host: string;
22
+ readonly port: number;
23
+ readonly verdict: PruneVerdict;
24
+ readonly reason: PruneReason;
25
+ }
26
+ /** Probe a single endpoint's `/health` — returns the body or null on failure. */
27
+ export type ProbeFn = (host: string, port: number) => Promise<HealthResponse | null>;
28
+ /** Options governing the liveness classification. */
29
+ export interface ClassifyOptions {
30
+ /** Extra probe attempts after the first failure (default 1 → 2 total). */
31
+ readonly retries?: number;
32
+ /** Delay between attempts in ms (default 0; the CLI uses a brief pause). */
33
+ readonly delayMs?: number;
34
+ /**
35
+ * Staleness TTL in ms for the DR-031 heartbeat signal (default
36
+ * `DEFAULT_REGISTRY_TTL_MS` = 15 min). An entry with a `last_heartbeat` older
37
+ * than this is treated as a (reinforcing) dead-signal.
38
+ */
39
+ readonly ttlMs?: number;
40
+ /** Injectable epoch-ms clock for deterministic staleness tests (default `Date.now()`). */
41
+ readonly now?: number;
42
+ }
43
+ /**
44
+ * Classify each peer as alive (keep) / dead (remove) via `probe`, with the
45
+ * DR-031 (groundnuty/macf#568) registry-heartbeat staleness as a COMPLEMENTARY
46
+ * dead-signal. A peer is removed only when:
47
+ *
48
+ * dead = (ALL liveness probes fail) AND (isStaleEntry OR no-heartbeat-data)
49
+ *
50
+ * Two conservative invariants fall out of that formula:
51
+ * - A SUCCESSFUL probe ALWAYS means alive — a responding agent is NEVER pruned,
52
+ * regardless of a stale heartbeat (a fresh process may not have beaten yet).
53
+ * - A FRESH heartbeat (beat within the TTL) keeps an entry even when /health is
54
+ * momentarily unreachable — the recent beat is a second, independent liveness
55
+ * signal that guards against pruning on a transient probe blip.
56
+ * A stale heartbeat only ever REINFORCES the dead verdict an unreachable probe
57
+ * already reaches (it never overrides a live probe). Entries with no
58
+ * `last_heartbeat` (pre-DR-031 / heartbeat-disabled) keep the legacy
59
+ * "dead iff all probes fail" behavior, since `isStaleEntry` is false for absence.
60
+ *
61
+ * PURE w.r.t. `probe`, `ttlMs`, and `now` — tests inject fakes. The probe retry
62
+ * is the "never remove on a single flaky read" guard.
63
+ */
64
+ export declare function classifyEntries(peers: readonly {
65
+ readonly name: string;
66
+ readonly info: AgentInfo;
67
+ }[], probe: ProbeFn, opts?: ClassifyOptions): Promise<readonly PruneEntry[]>;
68
+ /** Format a verdict line for the report (pure — exported for tests). */
69
+ export declare function formatVerdictLine(e: PruneEntry): string;
70
+ /** Options for `runRegistryPrune`. */
71
+ export interface RunRegistryPruneOptions {
72
+ /** Skip the confirmation prompt (non-interactive). */
73
+ readonly yes?: boolean;
74
+ }
75
+ /**
76
+ * `macf registry prune` entry point. Returns the shell exit code. Lists the
77
+ * registry, probes each entry over mTLS (with retry), prints per-entry
78
+ * verdicts, then removes ONLY confirmed-dead entries after consent.
79
+ */
80
+ export declare function runRegistryPrune(projectDir: string, opts?: RunRegistryPruneOptions): Promise<number>;
81
+ //# sourceMappingURL=registry-prune.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry-prune.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/registry-prune.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvE,4EAA4E;AAC5E,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,CAAC;AAE5C;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,WAAW,GACnB,YAAY,GACZ,kBAAkB,GAClB,iBAAiB,GACjB,aAAa,CAAC;AAElB,qCAAqC;AACrC,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED,iFAAiF;AACjF,MAAM,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;AAErF,qDAAqD;AACrD,MAAM,WAAW,eAAe;IAC9B,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,0FAA0F;IAC1F,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAKD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,EAAE,EACrE,KAAK,EAAE,OAAO,EACd,IAAI,CAAC,EAAE,eAAe,GACrB,OAAO,CAAC,SAAS,UAAU,EAAE,CAAC,CAoChC;AAED,wEAAwE;AACxE,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAYvD;AAaD,sCAAsC;AACtC,MAAM,WAAW,uBAAuB;IACtC,sDAAsD;IACtD,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE,uBAAuB,GAC7B,OAAO,CAAC,MAAM,CAAC,CA8DjB"}
@@ -0,0 +1,163 @@
1
+ /**
2
+ * `macf registry prune` — confirm-before-destroy registry maintenance (macf#556).
3
+ *
4
+ * Root hazard: a blind registry `DELETE` orphaned LIVE channel servers. This
5
+ * command mTLS-`/health`-checks each `MACF_AGENT_*` entry and removes ONLY
6
+ * confirmed-dead ones — and only after explicit operator consent (default No;
7
+ * `--yes` bypasses for automation). "Confirmed dead" means the probe failed
8
+ * across a brief retry: a single flaky read NEVER removes an entry.
9
+ *
10
+ * Reuses the existing building blocks: the mTLS `/health` ping
11
+ * (`pingAgentHealth`), the registry list/remove surface
12
+ * (`createRegistryFromConfig`), the CA-cert read (`createClientFromConfig`),
13
+ * and the consent-prompt pattern from `doctor.ts`.
14
+ */
15
+ import { createInterface } from 'node:readline';
16
+ import { readAgentConfig, tokenSourceFromConfig, agentCertPath, agentKeyPath, } from '../config.js';
17
+ import { createClientFromConfig } from '../registry-helper.js';
18
+ import { createRegistryFromConfig, generateToken, pingAgentHealth, toVariableSegment, isStaleEntry, DEFAULT_REGISTRY_TTL_MS, } from '@groundnuty/macf-core';
19
+ const sleep = (ms) => ms > 0 ? new Promise((r) => setTimeout(r, ms)) : Promise.resolve();
20
+ /**
21
+ * Classify each peer as alive (keep) / dead (remove) via `probe`, with the
22
+ * DR-031 (groundnuty/macf#568) registry-heartbeat staleness as a COMPLEMENTARY
23
+ * dead-signal. A peer is removed only when:
24
+ *
25
+ * dead = (ALL liveness probes fail) AND (isStaleEntry OR no-heartbeat-data)
26
+ *
27
+ * Two conservative invariants fall out of that formula:
28
+ * - A SUCCESSFUL probe ALWAYS means alive — a responding agent is NEVER pruned,
29
+ * regardless of a stale heartbeat (a fresh process may not have beaten yet).
30
+ * - A FRESH heartbeat (beat within the TTL) keeps an entry even when /health is
31
+ * momentarily unreachable — the recent beat is a second, independent liveness
32
+ * signal that guards against pruning on a transient probe blip.
33
+ * A stale heartbeat only ever REINFORCES the dead verdict an unreachable probe
34
+ * already reaches (it never overrides a live probe). Entries with no
35
+ * `last_heartbeat` (pre-DR-031 / heartbeat-disabled) keep the legacy
36
+ * "dead iff all probes fail" behavior, since `isStaleEntry` is false for absence.
37
+ *
38
+ * PURE w.r.t. `probe`, `ttlMs`, and `now` — tests inject fakes. The probe retry
39
+ * is the "never remove on a single flaky read" guard.
40
+ */
41
+ export async function classifyEntries(peers, probe, opts) {
42
+ const retries = opts?.retries ?? 1;
43
+ const delayMs = opts?.delayMs ?? 0;
44
+ const ttlMs = opts?.ttlMs ?? DEFAULT_REGISTRY_TTL_MS;
45
+ const now = opts?.now ?? Date.now();
46
+ const results = [];
47
+ for (const peer of peers) {
48
+ let probeAlive = false;
49
+ for (let attempt = 0; attempt <= retries; attempt++) {
50
+ const health = await probe(peer.info.host, peer.info.port);
51
+ if (health) {
52
+ probeAlive = true;
53
+ break;
54
+ }
55
+ if (attempt < retries)
56
+ await sleep(delayMs);
57
+ }
58
+ const stale = isStaleEntry(peer.info, ttlMs, now);
59
+ const hasHeartbeat = peer.info.last_heartbeat !== undefined;
60
+ // dead = !probeAlive && (stale || !hasHeartbeat). A live probe wins always;
61
+ // a fresh heartbeat keeps an unreachable entry; absence keeps legacy behavior.
62
+ let reason;
63
+ if (probeAlive)
64
+ reason = 'responding';
65
+ else if (stale)
66
+ reason = 'stale-heartbeat';
67
+ else if (!hasHeartbeat)
68
+ reason = 'unreachable';
69
+ else
70
+ reason = 'recent-heartbeat';
71
+ const verdict = reason === 'responding' || reason === 'recent-heartbeat' ? 'alive' : 'dead';
72
+ results.push({
73
+ name: peer.name,
74
+ host: peer.info.host,
75
+ port: peer.info.port,
76
+ verdict,
77
+ reason,
78
+ });
79
+ }
80
+ return results;
81
+ }
82
+ /** Format a verdict line for the report (pure — exported for tests). */
83
+ export function formatVerdictLine(e) {
84
+ const where = `${e.host}:${e.port}`;
85
+ const note = e.reason === 'responding'
86
+ ? 'alive → keep'
87
+ : e.reason === 'recent-heartbeat'
88
+ ? 'unreachable but heartbeat fresh → keep'
89
+ : e.reason === 'stale-heartbeat'
90
+ ? 'confirmed-dead (heartbeat stale) → remove'
91
+ : 'confirmed-dead → remove';
92
+ const glyph = e.verdict === 'alive' ? '✓' : '✗';
93
+ return ` ${glyph} ${e.name.padEnd(28)} ${where.padEnd(24)} ${note}`;
94
+ }
95
+ /** Prompt the operator for a y/N confirmation on stdin. Default = No. */
96
+ function promptYesNo(question) {
97
+ return new Promise((resolveAnswer) => {
98
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
99
+ rl.question(`${question} [y/N] `, (answer) => {
100
+ rl.close();
101
+ resolveAnswer(/^y(es)?$/i.test(answer.trim()));
102
+ });
103
+ });
104
+ }
105
+ /**
106
+ * `macf registry prune` entry point. Returns the shell exit code. Lists the
107
+ * registry, probes each entry over mTLS (with retry), prints per-entry
108
+ * verdicts, then removes ONLY confirmed-dead entries after consent.
109
+ */
110
+ export async function runRegistryPrune(projectDir, opts) {
111
+ const config = readAgentConfig(projectDir);
112
+ if (!config) {
113
+ console.error('No macf-agent.json found. Run `macf init` first.');
114
+ return 1;
115
+ }
116
+ if (config.registry.type === 'local') {
117
+ console.error('registry prune targets GitHub-backed registries; local-registry mode (DR-024) ' +
118
+ 'is not supported. Edit the local JSON registry file directly.');
119
+ return 1;
120
+ }
121
+ const token = await generateToken(tokenSourceFromConfig(projectDir, config));
122
+ const registry = createRegistryFromConfig(config.registry, config.project, token);
123
+ const client = createClientFromConfig(config.registry, token);
124
+ const caCertPem = await client.readVariable(`${toVariableSegment(config.project)}_CA_CERT`);
125
+ if (!caCertPem) {
126
+ console.error('CA certificate not found in registry. Run `macf certs init` first.');
127
+ return 1;
128
+ }
129
+ const peers = await registry.list('');
130
+ if (peers.length === 0) {
131
+ console.log('No registry entries to check.');
132
+ return 0;
133
+ }
134
+ const certPath = agentCertPath(projectDir);
135
+ const keyPath = agentKeyPath(projectDir);
136
+ const probe = (host, port) => pingAgentHealth({ host, port, caCertPem, certPath, keyPath });
137
+ console.log(`Probing ${peers.length} registry entr${peers.length === 1 ? 'y' : 'ies'} via mTLS /health …\n`);
138
+ const results = await classifyEntries(peers, probe, { retries: 1, delayMs: 750 });
139
+ for (const e of results)
140
+ console.log(formatVerdictLine(e));
141
+ const dead = results.filter((e) => e.verdict === 'dead');
142
+ if (dead.length === 0) {
143
+ console.log('\nAll entries responded — nothing to prune.');
144
+ return 0;
145
+ }
146
+ console.log(`\n${dead.length} confirmed-dead entr${dead.length === 1 ? 'y' : 'ies'} would be REMOVED:`);
147
+ for (const e of dead) {
148
+ const why = e.reason === 'stale-heartbeat' ? ' (heartbeat stale)' : '';
149
+ console.log(` - ${e.name}${why}`);
150
+ }
151
+ const consent = opts?.yes ? true : await promptYesNo('\nRemove these registry entries?');
152
+ if (!consent) {
153
+ console.log('Aborted — no entries removed.');
154
+ return 0;
155
+ }
156
+ for (const e of dead) {
157
+ await registry.remove(e.name);
158
+ console.log(` removed ${e.name}`);
159
+ }
160
+ console.log(`\nPruned ${dead.length} dead registry entr${dead.length === 1 ? 'y' : 'ies'}.`);
161
+ return 0;
162
+ }
163
+ //# sourceMappingURL=registry-prune.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry-prune.js","sourceRoot":"","sources":["../../../src/cli/commands/registry-prune.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ /** The three restart drivers (DR-031 §"Be-replaceable" — fault / upgrade / manual). */
2
+ export declare const RESTART_REASONS: readonly ["fault", "upgrade", "manual"];
3
+ export type RestartReason = (typeof RESTART_REASONS)[number];
4
+ /** Coerce an arbitrary `--reason` string to a known reason; defaults to `manual`. */
5
+ export declare function coerceReason(raw: string | undefined): RestartReason;
6
+ /** Result of a stash attempt. `stashed: false` when there was nothing to stash. */
7
+ export interface StashResult {
8
+ readonly stashed: boolean;
9
+ readonly ref?: string;
10
+ }
11
+ /**
12
+ * Every side effect `runRestartSelf` performs, injected so tests verify the
13
+ * orchestration WITHOUT real stashes / kills / spawns. The git READS (branch,
14
+ * HEAD, dirty-state) are side effects too, so they live here as well.
15
+ */
16
+ export interface RestartSelfDeps {
17
+ readonly now: () => Date;
18
+ readonly hasUncommittedTrackedChanges: () => boolean;
19
+ readonly currentBranch: () => string;
20
+ readonly headSha: () => string;
21
+ readonly stash: (label: string) => StashResult;
22
+ readonly writeFile: (path: string, content: string, mode?: number) => void;
23
+ readonly mkdirp: (path: string) => void;
24
+ readonly spawnDetached: (scriptPath: string, args: readonly string[]) => void;
25
+ readonly killSession: (session: string) => void;
26
+ }
27
+ /** Options for `runRestartSelf` (already-resolved identity; pure orchestrator input). */
28
+ export interface RunRestartSelfOptions {
29
+ /** Absolute workspace dir (holds `claude.sh` + `.claude/.macf/`). */
30
+ readonly workspaceDir: string;
31
+ /** Project name (for the `<project>@<agent>` session derivation). */
32
+ readonly project?: string;
33
+ /** Agent name (for the `<project>@<agent>` session derivation). */
34
+ readonly agentName?: string;
35
+ /** Explicit session override; when set it wins over the derived form. */
36
+ readonly session?: string;
37
+ readonly reason: RestartReason;
38
+ /** Without this, the command is DRY-RUN regardless. */
39
+ readonly confirm: boolean;
40
+ /** Force dry-run even with `--confirm` (the safer of the two wins). */
41
+ readonly dryRun: boolean;
42
+ readonly json: boolean;
43
+ }
44
+ /** The `--json` state-record (mirrors `fleet doctor`'s versioned shape). */
45
+ export declare const RESTART_SELF_JSON_SCHEMA_VERSION = 1;
46
+ export interface RestartSelfPlan {
47
+ readonly schema_version: number;
48
+ readonly dry_run: boolean;
49
+ readonly reason: RestartReason;
50
+ readonly session: string;
51
+ readonly stash_ref: string | null;
52
+ readonly resume_note_path: string;
53
+ readonly relauncher_path: string;
54
+ readonly killed: boolean;
55
+ }
56
+ /** Derive `<project>@<agent>` (the canonical claude.sh self-wrap session), or null. */
57
+ export declare function resolveSession(opts: RunRestartSelfOptions): string | null;
58
+ /** The marked-stash label: `macf-restart-self/<ISO-8601-ts>/<reason>`. */
59
+ export declare function stashLabel(iso: string, reason: RestartReason): string;
60
+ /** The RESUME-note body — what a future session needs to pick the work back up. */
61
+ export declare function buildResumeNote(args: {
62
+ readonly reason: RestartReason;
63
+ readonly iso: string;
64
+ readonly branch: string;
65
+ readonly head: string;
66
+ readonly stashRef: string | null;
67
+ }): string;
68
+ /**
69
+ * The detached relauncher script. Waits for the OLD session to die (up to ~30s),
70
+ * then `cd`s to the workspace, sources the host-prelude IF it exists (decoupled
71
+ * from DR-031 piece 4 — proceed if absent), and `exec`s the launcher. Uses
72
+ * absolute paths so it does not depend on the dying session's env beyond what it
73
+ * re-establishes.
74
+ */
75
+ export declare function buildRelauncherScript(args: {
76
+ readonly workspaceDir: string;
77
+ readonly session: string;
78
+ readonly iso: string;
79
+ }): string;
80
+ /**
81
+ * Pure orchestrator. Returns the shell exit code. DRY-RUN BY DEFAULT — only a
82
+ * `--confirm` (and not `--dry-run`) run stashes / writes / spawns / kills.
83
+ * Refuses (exit 1) when the session name cannot be resolved.
84
+ */
85
+ export declare function runRestartSelf(opts: RunRestartSelfOptions, deps: RestartSelfDeps): Promise<number>;
86
+ /**
87
+ * Real side-effect implementations bound to a workspace dir. Git reads/stash run
88
+ * in `workspaceDir`; the spawn is FULLY DETACHED (`detached: true` opens a new
89
+ * session — the Node equivalent of `setsid` — plus `stdio: 'ignore'` + `unref()`
90
+ * so the relauncher outlives this process when its session is killed).
91
+ */
92
+ export declare function createRealDeps(workspaceDir: string): RestartSelfDeps;
93
+ export interface RestartSelfCliOptions {
94
+ readonly reason?: string;
95
+ readonly confirm?: boolean;
96
+ readonly dryRun?: boolean;
97
+ readonly json?: boolean;
98
+ }
99
+ /**
100
+ * Resolve identity (workspace / project / agent) from env first (the running
101
+ * agent's `claude.sh`-exported values), falling back to `.macf/macf-agent.json`.
102
+ * The canonical session claude.sh self-wraps into is `${MACF_PROJECT}@${MACF_AGENT_NAME}`.
103
+ */
104
+ export declare function resolveIdentity(projectDir: string, env?: NodeJS.ProcessEnv): {
105
+ readonly workspaceDir: string;
106
+ readonly project?: string;
107
+ readonly agentName?: string;
108
+ };
109
+ /** `macf restart-self` entry point — resolves config, wires real deps, runs. */
110
+ export declare function runRestartSelfCommand(projectDir: string, cliOpts: RestartSelfCliOptions): Promise<number>;
111
+ //# sourceMappingURL=restart-self.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restart-self.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/restart-self.ts"],"names":[],"mappings":"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"}