holistic 0.5.5 → 0.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.1 - 2026-04-12
4
+
5
+ Trust & Privacy Hardening (M006). This release implements a "Consent-First" read-only architecture, strengthens privacy boundaries for portable state, and introduces configurable MCP logging and enhanced secret redaction.
6
+
7
+ - Implemented **Read-Only Command Policy**: Routine commands (`status`, `resume`, `diff`, `search`) are now strictly non-mutating. They will surface health warnings for outdated hooks but will never fix them silently.
8
+ - Hardened **Privacy Mode Enforcement**: When `portableState` is disabled (Privacy Mode), generated shell scripts and Git hooks now exit early to prevent any accidental remote state synchronization.
9
+ - Added **MCP Logging Privacy**: Introduced `mcpLogging` configuration (`off` | `minimal` | `default`). Defaults to `minimal` to prevent session objectives and titles from leaking into system logs.
10
+ - Expanded **Secret Redaction**: significantly strengthened the redaction engine to identify and scrub JWT tokens, Bearer tokens, AWS keys, and PEM private key blocks.
11
+ - Added **Redaction Quality Tests**: Integrated 8 new unit tests to verify that sensitive patterns are correctly scrubbed while preserving normal text.
12
+ - Added **SECURITY.md**: Published a comprehensive technical disclosure of Holistic's trust model, data residency guarantees, and safety architecture.
13
+
14
+ ## 0.6.0 - 2026-04-11
15
+
16
+ Comprehensive Reliability & UX Refinement (M005). This release finalizes the security hardening milestone, introduces granular bootstrap controls, and adds support for explicit portable-state management.
17
+
18
+ - Added **Granular Bootstrap Flags**: Users can now surgically enable setup items with `--yes-hooks`, `--yes-daemon`, `--yes-mcp`, `--yes-attr`, and `--yes-claude`.
19
+ - Added `--portable` flag to `init` and `bootstrap` to explicitly toggle **Portable State (Privacy Mode)** during setup.
20
+ - Refined **Bootstrap Pre-flight UX**: The pre-flight check now clearly differentiates between "Core Configuration" (covered by `--yes`) and "Optional/Explicit" items.
21
+ - Fixed **Runtime Script Resolution**: Resolved a critical production bug where the CLI incorrectly searched for `.ts` files in built environments; now correctly resolves `.js` files when TypeScript stripping is unavailable.
22
+ - Hardened **Read-Only Diagnostics**: Refactored `holistic doctor` and `getSetupStatus` to be strictly read-only, ensuring health checks never inadvertently modify Git hooks or repository state.
23
+ - Improved **MCP Server Transparency**: Sanitized startup logging to prevent context leakage in system logs while maintaining full context availability via the `holistic_resume` tool.
24
+ - Aligned **Claude Code Hook Detection**: Fixed a bug where `holistic doctor` misreported Claude hook status by incorrectly checking the filesystem instead of `settings.json`.
25
+
3
26
  ## 0.5.5 - 2026-04-10
4
27
 
5
28
  Major Security & Trust Hardening (M005) to eliminate silent automation and improve auditability.
package/README.md CHANGED
@@ -17,35 +17,35 @@ Shared memory for AI agents, built into your repo.
17
17
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
18
18
  [![Node.js](https://img.shields.io/badge/node-%3E%3D24-339933.svg)](./package.json)
19
19
 
20
- ### One command. Every agent. Zero re-explaining. ✨
20
+ ### One command. Every agent. Zero re-explaining. ✨
21
+ No context loss. No fragile handoffs.
21
22
 
22
23
  Holistic gives your AI agents shared memory inside the repo itself. When you switch from Claude to Codex to Gemini, the next agent can see what happened last time, what not to break, and what should happen next.
23
24
 
24
-
25
25
  ---
26
26
 
27
+ ## Why trust Holistic? 🔒
27
28
 
28
- ### Why trust this?
29
+ Holistic is designed to be **safe to install, inspectable, and predictable**.
29
30
 
30
- - 🔐 No known security vulnerabilities
31
- - 🧪 60+ automated tests covering core flows
31
+ - 🔐 **Security-first design** local-first, no telemetry, no external services
32
+ - 🧭 **Consent-first model** system changes only happen via `bootstrap` or `repair`
33
+ - 👀 **Read-only by default** — routine commands never silently modify your repo or machine
34
+ - 🔍 **Fully transparent** — readable scripts, visible hooks, no hidden behavior
35
+ - 🛡️ **Privacy mode by default** — no remote sync unless explicitly enabled
36
+ - 🧪 60+ automated tests covering core flows
32
37
  - 🛠️ Actively maintained (frequent releases)
33
- - 🔍 Transparent local setup (see `SECURITY.md`)
34
-
35
- > Early beta, but built to be safe, inspectable, and predictable.
36
38
 
39
+ > See [SECURITY.md](./SECURITY.md) for full technical details.
37
40
 
38
41
  ---
39
42
 
40
-
41
43
  ## Get started in 30 seconds ⚡
42
44
 
43
45
  Open your project repo in PowerShell, Terminal, Command Prompt, or whatever shell you normally use.
44
46
 
45
47
  Requires Node.js 24+.
46
48
 
47
- Run these two commands:
48
-
49
49
  ```bash
50
50
  npm install -g holistic
51
51
  holistic bootstrap --yes
@@ -61,26 +61,22 @@ That is enough to get the basic Holistic workflow working.
61
61
 
62
62
  If you want the fuller install and setup details, jump to [Quick start](#quick-start-).
63
63
 
64
-
65
64
  ---
66
65
 
67
-
68
66
  ## The problem 😵
69
67
 
70
68
  If you use more than one AI coding assistant, the workflow usually falls apart:
71
69
 
72
- - 🔁 You re-explain the project every session.
73
- - 🐞 Bugs come back because the next agent does not know what was already fixed.
74
- - 🧠 Progress gets lost when context windows end.
75
- - 💥 Agents undo each other because there is no durable handoff.
76
- - 🌫️ It is hard to tell what is actually done.
70
+ - 🔁 You re-explain the project every session
71
+ - 🐞 Bugs come back because the next agent does not know what was already fixed
72
+ - 🧠 Progress gets lost when context windows end
73
+ - 💥 Agents undo each other because there is no durable handoff
74
+ - 🌫️ It is hard to tell what is actually done
77
75
 
78
76
  Holistic fixes that by making the repo the source of truth.
79
77
 
80
-
81
78
  ---
82
79
 
83
-
84
80
  ## What it feels like with HOLISTIC 🌿
85
81
 
86
82
  Run one setup command on a machine:
@@ -91,19 +87,15 @@ holistic bootstrap
91
87
 
92
88
  Then daily use is mostly:
93
89
 
94
- 1. Open the repo in Codex, Claude, or another supported app.
95
- 2. Start a fresh session.
96
- 3. Ask the agent to read `AGENTS.md` and `HOLISTIC.md`.
97
- 4. Let Holistic carry continuity through checkpoints, handoffs, and repo memory.
90
+ 1. Open the repo in Codex, Claude, or another supported app
91
+ 2. Start a fresh session
92
+ 3. Ask the agent to read `AGENTS.md` and `HOLISTIC.md`
93
+ 4. Let Holistic carry continuity through checkpoints, handoffs, and repo memory
98
94
 
99
95
  Most days, you do not need to keep a terminal process open or manually re-brief the agent.
100
96
 
101
- `holistic bootstrap` is a machine setup command, not just a repo setup command. By default it can install local startup helpers and configure Claude Desktop MCP on that machine.
102
-
103
-
104
97
  ---
105
98
 
106
-
107
99
  ## How it works 🧭
108
100
 
109
101
  ```text
@@ -122,9 +114,24 @@ Holistic checkpoints and handoffs keep repo memory current
122
114
  The next agent picks up without a long re-explanation
123
115
  ```
124
116
 
125
-
126
117
  ---
127
118
 
119
+ ## 🔒 Security & Trust Model
120
+
121
+ Holistic is designed to be **transparent, audit-safe, and consent-first**.
122
+
123
+ - **Read-Only by Default** — routine commands warn instead of mutating
124
+ - **Explicit Consent** — system changes require `bootstrap` or `repair`
125
+ - **Granular Control** — apply only what you want (`--yes-*` flags)
126
+ - **Privacy Mode** — sync disabled by default with enforced guards
127
+ - **MCP Logging Controls** — configurable visibility
128
+ - **Redaction** — JWTs, tokens, AWS keys, and PEM blocks scrubbed
129
+ - **Traceable Activity** — logs written locally
130
+ - **Git-native behavior** — respects `.gitignore`
131
+
132
+ For full details, see [SECURITY.md](./SECURITY.md).
133
+
134
+ ---
128
135
 
129
136
  ## Quick start 🚀
130
137
 
@@ -178,12 +185,14 @@ Advanced overrides:
178
185
  ```bash
179
186
  holistic bootstrap --state-ref refs/holistic/state
180
187
  holistic bootstrap --state-branch holistic/state
188
+ holistic bootstrap --portable
181
189
  ```
182
190
 
183
- If you want repo scaffolding without changing local desktop integrations or daemon startup on the current machine, use:
191
+ If you want repo scaffolding without changing local desktop integrations or daemon startup on the current machine, you can use granular flags to be surgical:
184
192
 
185
193
  ```bash
186
- holistic bootstrap --install-daemon false --configure-mcp false
194
+ # Only install Git hooks and managed attributes
195
+ holistic bootstrap --yes-hooks --yes-attr
187
196
  ```
188
197
 
189
198
  **What to commit:**
@@ -208,7 +217,7 @@ One-time machine setup:
208
217
 
209
218
  - Run `holistic bootstrap`.
210
219
  - By default it scaffolds repo files, installs hooks, sets up daemon startup, and configures supported integrations such as Claude Desktop MCP on the current machine.
211
- - If you only want repo files and hooks, use `holistic bootstrap --install-daemon false --configure-mcp false`.
220
+ - To be surgical about what is applied, use granular flags like `--yes-hooks`, `--yes-daemon`, or `--yes-mcp`.
212
221
 
213
222
  Normal use:
214
223
 
@@ -307,8 +316,10 @@ The portable repo memory (config, state, context, sessions) is meant to be commi
307
316
 
308
317
  ## Commands
309
318
 
319
+ | Command | Description |
320
+ | :--- | :--- |
310
321
  | `holistic init` | Base repo setup and scaffolding |
311
- | `holistic bootstrap` | One-step machine setup. Required `--yes` to apply system changes. |
322
+ | `holistic bootstrap` | One-step machine setup. Required `--yes` for Core Setup, or granular `--yes-*` flags. |
312
323
  | `holistic doctor` | Runs health checks on machine setup and sync logs |
313
324
  | `holistic repair` | Regenerates `.holistic/system/` helpers |
314
325
  | `holistic resume / start --agent <name>` | Loads project recap and prints state |
@@ -411,17 +422,17 @@ For support and troubleshooting, see [SUPPORT.md](./SUPPORT.md).
411
422
  ---
412
423
 
413
424
 
414
- ## Security, Privacy, and Trust 🔒
415
-
416
425
  Holistic is designed to be **transparent, audit-safe, and consent-first**. It is a shared memory layer that stays in your repo, not a cloud service that watches your screen.
417
426
 
418
427
  ### Trust Architecture:
419
- - **Explicit Consent**: `holistic bootstrap` will show you exactly what changes it wants to make to your machine (daemon installation, git hooks, Claude setup) and requires an explicit `--yes` to proceed.
420
- - **Privacy First**: Remote syncing is **disabled by default**. Set `"portableState": true` in `.holistic/config.json` only if you want to share memory across devices using a hidden git ref.
421
- - **Traceable Activity**: Background sync operations (PowerShell/Bash) are no longer silent. All activity is logged with timestamps and error details to `.holistic/system/sync.log`.
422
- - **Health Checks**: Use `holistic doctor` at any time to audit your machine-local setup and verify sync log health.
428
+ - **Read-Only by Default**: Routine commands (`status`, `resume`, `diff`) are strictly non-mutating. They will only **warn** you if git hooks are missing or outdated, rather than fixing them silently.
429
+ - **Granular Consent**: `holistic bootstrap` uses a "Consent-First" model. It displays a summary of system-modifying actions and requires an explicit `--yes` for the Core Setup (hooks, daemon, MCP, attributes).
430
+ - **Surgical Control**: Use granular flags (`--yes-hooks`, `--yes-daemon`, `--yes-mcp`, `--yes-attr`, `--yes-claude`) to apply only the specific integrations you want.
431
+ - **Privacy Mode Enforcement**: When `portableState` is disabled (the default), all generated sync scripts and git hooks contain early-exit guards to prevent any accidental remote traffic.
432
+ - **MCP Logging Privacy**: Control what Holistic reports to your agent UI. Set `mcpLogging` to `"off"`, `"minimal"` (default), or `"default"` in `.holistic/config.json`.
433
+ - **Advanced Redaction**: Holistic automatically scrubs JWTs, Bearer tokens, AWS keys, and PEM blocks from all generated session metadata to prevent context leakage.
434
+ - **Traceable Activity**: Background sync operations (PowerShell/Bash) are visible and logged with timestamps to `.holistic/system/sync.log`.
423
435
  - **Git-Native Snapshotting**: The repo snapshot logic uses native `git ls-files`, ensuring that your `.gitignore` rules are perfectly respected and performance stays $O(\text{repo size})$.
424
- - **Zero Shell Injection**: Internal commit logic has been stripped of shell wrappers to eliminate command injection risks.
425
436
 
426
437
  ### What it does:
427
438
  - Writes session state into `.holistic/` inside your repo (committed files you control)
@@ -0,0 +1,5 @@
1
+ export declare const tests: {
2
+ name: string;
3
+ run: () => void;
4
+ }[];
5
+ //# sourceMappingURL=redact.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/redact.test.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,KAAK;;;GAuEjB,CAAC"}
@@ -0,0 +1,75 @@
1
+ import assert from "node:assert";
2
+ import { sanitizeText } from '../core/redact.js';
3
+ export const tests = [
4
+ {
5
+ name: "sanitizeText redacts OpenAI-style sk- keys",
6
+ run: () => {
7
+ const input = "Use key sk-Abc123Abc123Abc123 for access";
8
+ const output = sanitizeText(input);
9
+ assert.strictEqual(output, "Use key [REDACTED_SECRET] for access");
10
+ },
11
+ },
12
+ {
13
+ name: "sanitizeText redacts GitHub PATs",
14
+ run: () => {
15
+ const input = "My token is ghp_foobarbazqux123";
16
+ const output = sanitizeText(input);
17
+ assert.strictEqual(output, "My token is [REDACTED_SECRET]");
18
+ },
19
+ },
20
+ {
21
+ name: "sanitizeText redacts JWT tokens",
22
+ run: () => {
23
+ const input = "Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoyNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
24
+ const output = sanitizeText(input);
25
+ assert.strictEqual(output, "Authorization: [REDACTED_JWT]");
26
+ },
27
+ },
28
+ {
29
+ name: "sanitizeText redacts Bearer tokens",
30
+ run: () => {
31
+ const input = "Header: Bearer abc.123.def-456";
32
+ const output = sanitizeText(input);
33
+ assert.strictEqual(output, "Header: Bearer [REDACTED]");
34
+ },
35
+ },
36
+ {
37
+ name: "sanitizeText redacts AWS Access Key IDs",
38
+ run: () => {
39
+ const input = "AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE";
40
+ const output = sanitizeText(input);
41
+ assert.strictEqual(output, "AWS_ACCESS_KEY_ID=[REDACTED_AWS_KEY]");
42
+ },
43
+ },
44
+ {
45
+ name: "sanitizeText redacts PEM Private Key blocks",
46
+ run: () => {
47
+ const input = `Here is the key:
48
+ -----BEGIN RSA PRIVATE KEY-----
49
+ MIIEpQIBAAKCAQEA75v...
50
+ ...more...
51
+ -----END RSA PRIVATE KEY-----
52
+ Keep it safe.`;
53
+ const output = sanitizeText(input);
54
+ assert.ok(output.includes("[REDACTED_PEM_PRIVATE_KEY]"));
55
+ assert.ok(!output.includes("MIIEpQIBAAKCAQEA75v"));
56
+ },
57
+ },
58
+ {
59
+ name: "sanitizeText redacts assignment-style secrets",
60
+ run: () => {
61
+ const input = "password: mypassword123; secret=shhh";
62
+ const output = sanitizeText(input);
63
+ assert.strictEqual(output, "password: [REDACTED]; secret: [REDACTED]");
64
+ },
65
+ },
66
+ {
67
+ name: "sanitizeText preserves normal text",
68
+ run: () => {
69
+ const input = "The quick brown fox jumps over the lazy dog.";
70
+ const output = sanitizeText(input);
71
+ assert.strictEqual(output, input);
72
+ },
73
+ },
74
+ ];
75
+ //# sourceMappingURL=redact.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.test.js","sourceRoot":"","sources":["../../src/__tests__/redact.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB;QACE,IAAI,EAAE,4CAA4C;QAClD,GAAG,EAAE,GAAG,EAAE;YACR,MAAM,KAAK,GAAG,0CAA0C,CAAC;YACzD,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;YACnC,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,sCAAsC,CAAC,CAAC;QACrE,CAAC;KACF;IACD;QACE,IAAI,EAAE,kCAAkC;QACxC,GAAG,EAAE,GAAG,EAAE;YACR,MAAM,KAAK,GAAG,iCAAiC,CAAC;YAChD,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;YACnC,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,+BAA+B,CAAC,CAAC;QAC9D,CAAC;KACF;IACD;QACE,IAAI,EAAE,iCAAiC;QACvC,GAAG,EAAE,GAAG,EAAE;YACR,MAAM,KAAK,GAAG,4KAA4K,CAAC;YAC3L,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;YACnC,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,+BAA+B,CAAC,CAAC;QAC9D,CAAC;KACF;IACD;QACE,IAAI,EAAE,oCAAoC;QAC1C,GAAG,EAAE,GAAG,EAAE;YACR,MAAM,KAAK,GAAG,gCAAgC,CAAC;YAC/C,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;YACnC,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,2BAA2B,CAAC,CAAC;QAC1D,CAAC;KACF;IACD;QACE,IAAI,EAAE,yCAAyC;QAC/C,GAAG,EAAE,GAAG,EAAE;YACR,MAAM,KAAK,GAAG,wCAAwC,CAAC;YACvD,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;YACnC,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,sCAAsC,CAAC,CAAC;QACrE,CAAC;KACF;IACD;QACE,IAAI,EAAE,6CAA6C;QACnD,GAAG,EAAE,GAAG,EAAE;YACR,MAAM,KAAK,GAAG;;;;;cAKN,CAAC;YACT,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;YACnC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,4BAA4B,CAAC,CAAC,CAAC;YACzD,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAC,CAAC,CAAC;QACrD,CAAC;KACF;IACD;QACE,IAAI,EAAE,+CAA+C;QACrD,GAAG,EAAE,GAAG,EAAE;YACR,MAAM,KAAK,GAAG,sCAAsC,CAAC;YACrD,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;YACnC,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,0CAA0C,CAAC,CAAC;QACzE,CAAC;KACF;IACD;QACE,IAAI,EAAE,oCAAoC;QAC1C,GAAG,EAAE,GAAG,EAAE;YACR,MAAM,KAAK,GAAG,8CAA8C,CAAC;YAC7D,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;YACnC,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACpC,CAAC;KACF;CACF,CAAC"}
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAA4C,YAAY,EAAE,aAAa,EAAgB,WAAW,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AA2EvJ,wBAAgB,cAAc,IAAI,MAAM,CA4BvC;AAMD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AA4ED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,GAAG,YAAY,CAcnG;AAMD,wBAAgB,UAAU,CAAC,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,CAoE1G;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,CA8D1E"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAA4C,YAAY,EAAE,aAAa,EAAgB,WAAW,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AA2EvJ,wBAAgB,cAAc,IAAI,MAAM,CA4BvC;AAMD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAmFD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,GAAG,YAAY,CAcnG;AAMD,wBAAgB,UAAU,CAAC,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,CAoE1G;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,CA8D1E"}
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import { renderRepoLocalCliCommands } from './core/cli-fallback.js';
7
7
  import { captureRepoSnapshot, clearPendingCommit, commitPendingChanges, writePendingCommit } from './core/git.js';
8
8
  import { writeDerivedDocs } from './core/docs.js';
9
- import { bootstrapHolistic, getSetupStatus, initializeHolistic, refreshHolisticHooks, repairHolistic } from './core/setup.js';
9
+ import { bootstrapHolistic, checkHolisticHooksStatus, getSetupStatus, initializeHolistic, refreshHolisticHooks, repairHolistic } from './core/setup.js';
10
10
  import { printSplash, printSplashError, renderSplash } from './core/splash.js';
11
11
  import { requestAutoSync } from './core/sync.js';
12
12
  import { runDaemonTick } from './daemon.js';
@@ -108,6 +108,12 @@ function reportHookWarnings(warnings) {
108
108
  process.stderr.write(`${warning}\n`);
109
109
  }
110
110
  }
111
+ function warnIfHooksOutdated(rootDir) {
112
+ const result = checkHolisticHooksStatus(rootDir);
113
+ if (result.refreshed.length > 0) {
114
+ process.stderr.write(`\u26A0 Holistic hooks are outdated. Run 'holistic repair' to refresh them.\n`);
115
+ }
116
+ }
111
117
  function refreshHooksBeforeCommand(rootDir) {
112
118
  const hookResult = refreshHolisticHooks(rootDir);
113
119
  reportHookWarnings(hookResult.warnings);
@@ -135,7 +141,7 @@ function runtimeScript(name) {
135
141
  const runtimeDir = path.dirname(currentFile);
136
142
  const useStripTypes = extension === ".ts";
137
143
  return {
138
- scriptPath: path.resolve(runtimeDir, `${name}${useStripTypes ? ".ts" : ".ts"}`),
144
+ scriptPath: path.resolve(runtimeDir, `${name}${useStripTypes ? ".ts" : ".js"}`),
139
145
  useStripTypes,
140
146
  };
141
147
  }
@@ -307,14 +313,17 @@ async function handleInit(rootDir, parsed) {
307
313
  const platformFlag = firstFlag(parsed.flags, "platform", process.platform);
308
314
  const platform = platformFlag === "windows" ? "win32" : platformFlag === "macos" ? "darwin" : platformFlag === "linux" ? "linux" : platformFlag;
309
315
  const intervalSeconds = Number.parseInt(firstFlag(parsed.flags, "interval", "30"), 10);
316
+ const portableState = firstFlag(parsed.flags, "portable") === "true" ? true : firstFlag(parsed.flags, "portable") === "false" ? false : undefined;
310
317
  const result = initializeHolistic(rootDir, {
311
- installDaemon: firstFlag(parsed.flags, "install-daemon") === "true",
312
- installGitHooks: firstFlag(parsed.flags, "install-hooks") === "true",
318
+ installDaemon: firstFlag(parsed.flags, "yes-daemon") === "true",
319
+ installGitHooks: firstFlag(parsed.flags, "yes-hooks") === "true",
320
+ installGitAttributes: firstFlag(parsed.flags, "yes-attr", "true") !== "false",
313
321
  platform: platform,
314
322
  intervalSeconds,
315
323
  remote: firstFlag(parsed.flags, "remote", "origin"),
316
324
  stateRef: firstFlag(parsed.flags, "state-ref"),
317
325
  stateBranch: firstFlag(parsed.flags, "state-branch"),
326
+ portableState,
318
327
  });
319
328
  reportHookWarnings(result.gitHookWarnings);
320
329
  const statusItems = [];
@@ -357,18 +366,33 @@ async function handleBootstrap(rootDir, parsed) {
357
366
  printSplash({
358
367
  message: "bootstrap pre-flight: pending actions",
359
368
  });
360
- process.stdout.write("\nHolistic needs to make the following changes to your system:\n\n");
369
+ process.stdout.write("\nHolistic needs to make the following changes to your system (some outside this repo):\n\n");
361
370
  printSetupStatusTable(status);
362
- process.stdout.write("\nRun with --yes to apply these changes.\n");
371
+ process.stdout.write("\nRun with --yes to apply the Core Configuration:\n");
372
+ process.stdout.write(" ✓ Git hooks, Background daemon, MCP config, Git attributes\n");
373
+ process.stdout.write("\nOptional / Explicit flags:\n");
374
+ process.stdout.write(" --yes-claude Install Claude Code SessionStart hooks\n");
375
+ process.stdout.write(" --portable Enable Portable State (remote-sync via git ref)\n");
376
+ process.stdout.write("\nGranular overrides:\n");
377
+ process.stdout.write(" --yes-hooks, --yes-daemon, --yes-mcp, --yes-attr\n");
363
378
  return 1;
364
379
  }
365
380
  printSplash({
366
381
  message: "bootstrapping holistic on this machine...",
367
382
  });
383
+ const installDaemon = firstFlag(parsed.flags, "yes-daemon") === "true" || (confirmed && firstFlag(parsed.flags, "install-daemon", "true") !== "false");
384
+ const installGitHooks = firstFlag(parsed.flags, "yes-hooks") === "true" || (confirmed && firstFlag(parsed.flags, "install-hooks", "true") !== "false");
385
+ const configureMcp = firstFlag(parsed.flags, "yes-mcp") === "true" || (confirmed && firstFlag(parsed.flags, "configure-mcp", "true") !== "false");
386
+ const installGitAttributes = firstFlag(parsed.flags, "yes-attr") === "true" || confirmed;
387
+ const installClaudeHooks = firstFlag(parsed.flags, "yes-claude") === "true";
388
+ const portableState = firstFlag(parsed.flags, "portable") === "true" ? true : firstFlag(parsed.flags, "portable") === "false" ? false : undefined;
368
389
  const result = bootstrapHolistic(rootDir, {
369
- installDaemon: firstFlag(parsed.flags, "install-daemon", "true") !== "false",
370
- installGitHooks: firstFlag(parsed.flags, "install-hooks", "true") !== "false",
371
- configureMcp: firstFlag(parsed.flags, "configure-mcp", "true") !== "false",
390
+ installDaemon,
391
+ installGitHooks,
392
+ installGitAttributes,
393
+ installClaudeHooks,
394
+ configureMcp,
395
+ portableState,
372
396
  platform: platform,
373
397
  intervalSeconds,
374
398
  remote: firstFlag(parsed.flags, "remote", "origin"),
@@ -471,7 +495,7 @@ Repo-local CLI: ${repairFallback}
471
495
  return 0;
472
496
  }
473
497
  async function handleResume(rootDir, parsed) {
474
- refreshHooksBeforeCommand(rootDir);
498
+ warnIfHooksOutdated(rootDir);
475
499
  const agent = asAgent(firstFlag(parsed.flags, "agent", "unknown"));
476
500
  if (firstFlag(parsed.flags, "continue") === "true") {
477
501
  const mutateResult = mutateState(rootDir, (state) => continueFromLatest(rootDir, state, agent));
@@ -508,7 +532,6 @@ async function handleResume(rootDir, parsed) {
508
532
  return 0;
509
533
  }
510
534
  async function handleCheckpoint(rootDir, parsed) {
511
- refreshHooksBeforeCommand(rootDir);
512
535
  const mutateResult = mutateState(rootDir, (state, paths) => {
513
536
  const regressions = listFlag(parsed.flags, "regression");
514
537
  const fixed = firstFlag(parsed.flags, "fixed");
@@ -559,11 +582,17 @@ async function handleCheckpoint(rootDir, parsed) {
559
582
  return 0;
560
583
  }
561
584
  async function handleStartNew(rootDir, parsed) {
562
- refreshHooksBeforeCommand(rootDir);
563
585
  const agent = asAgent(firstFlag(parsed.flags, "agent", "unknown"));
564
- const goal = firstFlag(parsed.flags, "goal");
586
+ let goal = firstFlag(parsed.flags, "goal");
565
587
  const title = firstFlag(parsed.flags, "title");
566
588
  const plan = listFlag(parsed.flags, "plan");
589
+ if (!goal) {
590
+ goal = await ask("Current objective / goal");
591
+ }
592
+ if (!goal) {
593
+ process.stderr.write("Error: A goal is required to start a new session.\n");
594
+ return 1;
595
+ }
567
596
  const mutateResult = mutateState(rootDir, (state) => startNewSession(rootDir, state, agent, goal, plan, title));
568
597
  if (!mutateResult.success || !mutateResult.state) {
569
598
  process.stderr.write(`Error: Failed to start session: ${mutateResult.error}\n`);
@@ -573,7 +602,6 @@ async function handleStartNew(rootDir, parsed) {
573
602
  return 0;
574
603
  }
575
604
  async function handleHandoff(rootDir, parsed) {
576
- refreshHooksBeforeCommand(rootDir);
577
605
  const { state, paths } = loadState(rootDir);
578
606
  if (!state.activeSession) {
579
607
  process.stderr.write("No active session to hand off.\n");
@@ -734,7 +762,7 @@ function renderSyncStatus(rootDir) {
734
762
  return lines.join("\n") + "\n";
735
763
  }
736
764
  async function handleStatus(rootDir) {
737
- refreshHooksBeforeCommand(rootDir);
765
+ warnIfHooksOutdated(rootDir);
738
766
  const { state } = loadState(rootDir);
739
767
  process.stdout.write(renderStatus(rootDir, state));
740
768
  return 0;
@@ -806,7 +834,6 @@ async function handleSearch(rootDir, parsed) {
806
834
  return 0;
807
835
  }
808
836
  async function handleServe(rootDir) {
809
- refreshHooksBeforeCommand(rootDir);
810
837
  printSplashError({
811
838
  message: "starting MCP server on stdio...",
812
839
  });
@@ -838,7 +865,6 @@ async function handleMarkCommit(rootDir, parsed) {
838
865
  return 0;
839
866
  }
840
867
  async function handleWatch(rootDir, parsed) {
841
- refreshHooksBeforeCommand(rootDir);
842
868
  const intervalSeconds = Number.parseInt(firstFlag(parsed.flags, "interval", "60"), 10);
843
869
  const agent = asAgent(firstFlag(parsed.flags, "agent", "unknown"));
844
870
  process.stdout.write(`Watching repo every ${intervalSeconds}s for checkpoint-worthy changes.\n`);