@bvdm/delano 0.2.3 → 0.2.5

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 (40) hide show
  1. package/.delano/viewer/README.md +3 -2
  2. package/.delano/viewer/public/app.js +13 -1
  3. package/.delano/viewer/public/app.jsx +2312 -0
  4. package/.delano/viewer/public/delano-mark.svg +4 -0
  5. package/.delano/viewer/public/index.html +12 -14
  6. package/.delano/viewer/public/styles.css +1005 -833
  7. package/.delano/viewer/server.js +46 -5
  8. package/README.md +63 -3
  9. package/assets/install-manifest.json +7 -0
  10. package/assets/payload/.agents/adapters/manifest.schema.json +103 -0
  11. package/assets/payload/.agents/adapters/spec-kit/adapter.json +71 -0
  12. package/assets/payload/.agents/hooks/README.md +6 -1
  13. package/assets/payload/.agents/hooks/codex-session-status.js +123 -0
  14. package/assets/payload/.agents/schemas/status-transitions.json +35 -0
  15. package/assets/payload/.agents/scripts/README.md +1 -1
  16. package/assets/payload/.agents/scripts/check-status-transitions.mjs +171 -2
  17. package/assets/payload/.agents/scripts/pm/import-spec-kit.sh +605 -0
  18. package/assets/payload/.agents/scripts/pm/init.sh +31 -2
  19. package/assets/payload/.agents/scripts/pm/research.sh +296 -0
  20. package/assets/payload/.agents/scripts/pm/status.sh +135 -28
  21. package/assets/payload/.agents/scripts/pm/validate.sh +16 -0
  22. package/assets/payload/.codex/hooks.json +17 -0
  23. package/assets/payload/.delano/viewer/README.md +3 -2
  24. package/assets/payload/.delano/viewer/public/app.js +13 -1
  25. package/assets/payload/.delano/viewer/public/index.html +12 -14
  26. package/assets/payload/.delano/viewer/public/styles.css +1005 -833
  27. package/assets/payload/.delano/viewer/server.js +46 -5
  28. package/assets/payload/.project/templates/decisions.md +18 -0
  29. package/assets/payload/.project/templates/plan.md +17 -0
  30. package/assets/payload/.project/templates/spec.md +12 -0
  31. package/assets/payload/.project/templates/task.md +6 -0
  32. package/assets/payload/.project/templates/workstream.md +1 -0
  33. package/package.json +4 -2
  34. package/src/cli/commands/install.js +2 -1
  35. package/src/cli/commands/state.js +689 -0
  36. package/src/cli/commands/viewer.js +2 -1
  37. package/src/cli/commands/wrapper.js +29 -5
  38. package/src/cli/index.js +120 -7
  39. package/src/cli/lib/install.js +179 -2
  40. package/src/cli/lib/project-state.js +918 -0
@@ -12,7 +12,16 @@ const { spawn, spawnSync } = require('node:child_process');
12
12
  const repoRoot = path.resolve(process.env.DELANO_VIEWER_ROOT || path.resolve(__dirname, '..', '..'));
13
13
  const projectRoot = path.join(repoRoot, '.project');
14
14
  const publicRoot = path.join(__dirname, 'public');
15
- const port = Number(process.env.DELANO_VIEWER_PORT || process.env.PORT || 3977);
15
+ const DEFAULT_PORT = 3977;
16
+ const MAX_PORT = 65535;
17
+ const MAX_PORT_ATTEMPTS = 100;
18
+ const startPort = normalizePort(process.env.DELANO_VIEWER_PORT || process.env.PORT, DEFAULT_PORT);
19
+
20
+ function normalizePort(value, fallback) {
21
+ const parsed = Number(value || fallback);
22
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > MAX_PORT) return fallback;
23
+ return parsed;
24
+ }
16
25
 
17
26
  function isInside(parent, child) {
18
27
  const rel = path.relative(parent, child);
@@ -332,6 +341,7 @@ function sendStatic(res, pathname) {
332
341
  const ext = path.extname(resolved).toLowerCase();
333
342
  const mimeMap = {
334
343
  '.js': 'text/javascript',
344
+ '.jsx': 'text/javascript',
335
345
  '.css': 'text/css',
336
346
  '.svg': 'image/svg+xml',
337
347
  '.png': 'image/png',
@@ -340,7 +350,7 @@ function sendStatic(res, pathname) {
340
350
  '.webp': 'image/webp',
341
351
  '.ico': 'image/x-icon',
342
352
  };
343
- const isText = ext === '.js' || ext === '.css' || ext === '.svg' || ext === '' || ext === '.html';
353
+ const isText = ext === '.js' || ext === '.jsx' || ext === '.css' || ext === '.svg' || ext === '' || ext === '.html';
344
354
  const type = mimeMap[ext] || 'text/html';
345
355
  const headers = isText ? { 'content-type': `${type}; charset=utf-8` } : { 'content-type': type };
346
356
  res.writeHead(200, headers);
@@ -384,6 +394,37 @@ const server = http.createServer((req, res) => {
384
394
  }
385
395
  });
386
396
 
387
- server.listen(port, '127.0.0.1', () => {
388
- console.log(`Delano read-only viewer: http://127.0.0.1:${port}`);
389
- });
397
+ function listenWithPortFallback(server, firstPort, host = '127.0.0.1') {
398
+ let port = firstPort;
399
+ let attempts = 0;
400
+
401
+ const listen = () => {
402
+ server.once('error', onError);
403
+ server.listen(port, host);
404
+ };
405
+
406
+ const onError = (error) => {
407
+ if (error.code === 'EADDRINUSE' && port < MAX_PORT && attempts < MAX_PORT_ATTEMPTS) {
408
+ attempts += 1;
409
+ port += 1;
410
+ listen();
411
+ return;
412
+ }
413
+
414
+ console.error(`Failed to start Delano viewer on ${host}:${port}: ${error.message}`);
415
+ process.exitCode = 1;
416
+ };
417
+
418
+ const onListening = () => {
419
+ server.removeListener('error', onError);
420
+ const address = server.address();
421
+ const actualPort = typeof address === 'object' && address ? address.port : port;
422
+ const skipped = actualPort !== firstPort ? ` (${firstPort} was unavailable)` : '';
423
+ console.log(`Delano read-only viewer: http://${host}:${actualPort}${skipped}`);
424
+ };
425
+
426
+ server.on('listening', onListening);
427
+ listen();
428
+ }
429
+
430
+ listenWithPortFallback(server, startPort);
package/README.md CHANGED
@@ -15,7 +15,7 @@ The npm package is intentionally thin. It distributes the approved runtime paylo
15
15
  ## Delano CLI
16
16
 
17
17
  - Package: `@bvdm/delano`
18
- - Current package version: `0.2.3`
18
+ - Current package version: `0.2.4`
19
19
  - Binary: `delano`
20
20
  - Commands: `onboarding`, `install`, `viewer`, `init`, `validate`, `status`, `next`
21
21
  - Primary goal: bootstrap a repo safely, expose local delivery state clearly, and keep runtime gates verifiable
@@ -72,6 +72,8 @@ delano install --yes
72
72
  delano viewer
73
73
  delano validate
74
74
  delano init <slug> "<Project Name>" [owner] [lead]
75
+ delano import-spec-kit <slug> <source-md> [--name <project-name>] [--owner <owner>] [--lead <lead>] [--json]
76
+ delano research <project-slug> <research-slug> [--title <title>] [--question <question>] [--json]
75
77
  ```
76
78
 
77
79
  Command intent:
@@ -80,6 +82,8 @@ Command intent:
80
82
  - `delano viewer` launches the read-only local UI for `.project` contracts
81
83
  - `delano validate` checks whether the runtime and required assets are in place
82
84
  - `delano init` creates a delivery project inside a repository that already has Delano installed
85
+ - `delano import-spec-kit` creates a planned Delano project from the first supported Spec Kit-style markdown fixture and then runs validation
86
+ - `delano research` creates repo-native research intake files inside an existing Delano project and then runs validation
83
87
 
84
88
  `delano init` usage:
85
89
 
@@ -94,8 +98,36 @@ Notes:
94
98
  - `lead` defaults to `owner`
95
99
  - this is the right command for an agent to scaffold a new delivery project after `delano install`
96
100
 
101
+ `delano import-spec-kit` usage:
102
+
103
+ ```bash
104
+ delano import-spec-kit <slug> <source-md> [--name <project-name>] [--owner <owner>] [--lead <lead>] [--json]
105
+ ```
106
+
107
+ Notes:
108
+
109
+ - the source markdown must use the initial supported shape documented in `docs/spec-kit/import-contract.md`
110
+ - agents should prefer named options over positional metadata, and `--json` when parsing the result
111
+ - imported artifacts start in planned/ready states and still have to pass Delano validation, probe, and evidence gates
112
+ - the command is additive and refuses to overwrite an existing `.project/projects/<slug>/` folder
113
+
114
+ `delano research` usage:
115
+
116
+ ```bash
117
+ delano research <project-slug> <research-slug> [--title <title>] [--question <question>] [--owner <owner>] [--json]
118
+ ```
119
+
120
+ Notes:
121
+
122
+ - use this before mutating `spec.md`, `plan.md`, workstreams, or tasks when intent is unclear
123
+ - research files live under `.project/projects/<project-slug>/research/<research-slug>/`
124
+ - agents should use `--json` when parsing the result
125
+ - research findings must fold forward into canonical Delano artifacts or be explicitly closed as no-action
126
+
97
127
  ## How to use Delano after install
98
128
 
129
+ For a fast guided path, start with [Delano in the First 15 Minutes](docs/first-15-minutes.md).
130
+
99
131
  Recommended first step:
100
132
 
101
133
  ```bash
@@ -111,6 +143,7 @@ npx -y @bvdm/delano@latest onboarding --approve-agents-analysis
111
143
  npx -y @bvdm/delano@latest viewer
112
144
  npx -y @bvdm/delano@latest validate
113
145
  npx -y @bvdm/delano@latest status
146
+ npx -y @bvdm/delano@latest status --open --brief
114
147
  npx -y @bvdm/delano@latest next -- --all
115
148
  ```
116
149
 
@@ -121,8 +154,11 @@ delano onboarding
121
154
  delano viewer
122
155
  delano validate
123
156
  delano status
157
+ delano status --open --brief
124
158
  delano next -- --all
125
159
  delano init <slug> "<Project Name>" [owner] [lead]
160
+ delano import-spec-kit <slug> <source-md> --json
161
+ delano research <project-slug> <research-slug> --title "Research title" --question "Primary question" --json
126
162
  ```
127
163
 
128
164
  The wrapper commands call the existing PM scripts under `.agents/scripts/pm/`. You can also run those scripts directly:
@@ -130,7 +166,9 @@ The wrapper commands call the existing PM scripts under `.agents/scripts/pm/`. Y
130
166
  ```bash
131
167
  bash .agents/scripts/pm/validate.sh
132
168
  bash .agents/scripts/pm/status.sh
169
+ bash .agents/scripts/pm/status.sh --open --brief
133
170
  bash .agents/scripts/pm/next.sh --all
171
+ bash .agents/scripts/pm/research.sh <project-slug> <research-slug> --title "Research title" --question "Primary question" --json
134
172
  ```
135
173
 
136
174
  The viewer is packaged with `@bvdm/delano` and serves the selected repository's `.project` files read-only. It defaults to `http://127.0.0.1:3977`; set `DELANO_VIEWER_PORT` or `PORT` to use another port. It indexes `.project/context`, `.project/templates`, and `.project/projects`, then derives artifact roles, statuses, project outlines, task/workstream relationships, snippets, and rendered markdown for local inspection.
@@ -160,6 +198,28 @@ The CLI does not bundle its own shell or Python runtime.
160
198
 
161
199
  The base install payload intentionally excludes top-level adapter entry docs such as `AGENTS.md`, `CLAUDE.md`, `CODEX.md`, `OPENCODE.md`, and `PI.md`. Those remain opt-in only.
162
200
  The base install payload includes `.delano/`, including the read-only viewer UI.
201
+ The base install payload also includes `.codex/hooks.json`, a Codex `SessionStart` hook config that injects compact open-project context when Codex hooks are enabled. If a target repo already has `.codex/hooks.json`, `delano install` merges the Delano hook into the existing JSON instead of replacing it. Invalid or non-file hook configs are skipped without blocking the rest of the install.
202
+
203
+ Codex hook activation is intentionally manual:
204
+
205
+ 1. Enable hooks for a session with `codex --enable hooks`, or persist the feature in `~/.codex/config.toml`:
206
+
207
+ ```toml
208
+ [features]
209
+ hooks = true
210
+ ```
211
+
212
+ 2. Start Codex in the repository and approve the project trust prompt for the repo-local `.codex/` layer. Codex records trusted projects in `~/.codex/config.toml`, for example:
213
+
214
+ ```toml
215
+ [projects."E:\\path\\to\\repo"]
216
+ trust_level = "trusted"
217
+ ```
218
+
219
+ 3. Approve the Delano `SessionStart` hook when Codex asks whether to trust it.
220
+
221
+ Older docs and builds may refer to `[features].codex_hooks`; newer Codex builds warn that this key is deprecated in favor of `[features].hooks`.
222
+
163
223
  The installable `.project/context/` pack is seeded from generic templates during packaging; it does not ship Delano's own repo-specific context files into consumer repositories.
164
224
  After install, the recommended first step is `delano onboarding`, which requires explicit approval before it reviews `AGENTS.md`.
165
225
 
@@ -174,7 +234,7 @@ delano install --no-project-state --force --yes
174
234
 
175
235
  The interactive installer presents presets for updating the runtime while preserving project state, updating only skills and project templates, full install or repair, and custom category selection.
176
236
 
177
- Install categories are `agent-runtime`, `skills`, `viewer`, `project-context`, `project-templates`, `project-registry`, `project-projects`, `handbook`, and `legacy-installer`. The `--no-project-state` shortcut excludes `.project/context`, `.project/projects`, and `.project/registry`.
237
+ Install categories are `agent-runtime`, `codex-hooks`, `skills`, `viewer`, `project-context`, `project-templates`, `project-registry`, `project-projects`, `handbook`, and `legacy-installer`. The `--no-project-state` shortcut excludes `.project/context`, `.project/projects`, and `.project/registry`.
178
238
 
179
239
  ## Optional AGENTS.md / CLAUDE.md snippet
180
240
 
@@ -254,7 +314,7 @@ Before the first Actions publish, configure npm trusted publishing for `@bvdm/de
254
314
 
255
315
  The package metadata must keep `repository.url` set to `https://github.com/MajesteitBart/delano`; npm validates that value against the GitHub Actions provenance bundle.
256
316
 
257
- After trusted publishing is configured, publish by pushing a matching version tag such as `v0.2.3`, or run the `Publish package to npm` workflow manually from `main`. The workflow rebuilds the package payload, checks manifest drift, runs tests, dry-runs the package contents, verifies the version is not already published, and then runs `npm publish --access public` from GitHub Actions using OIDC. A manual `dry_run` input is available to run the same checks without publishing.
317
+ After trusted publishing is configured, publish by pushing a matching version tag such as `v0.2.4`, or run the `Publish package to npm` workflow manually from `main`. The workflow rebuilds the package payload, checks manifest drift, runs tests, dry-runs the package contents, verifies the version is not already published, and then runs `npm publish --access public` from GitHub Actions using OIDC. A manual `dry_run` input is available to run the same checks without publishing.
258
318
 
259
319
  If npm publish fails after the package checks pass, verify that the npm trusted publisher settings match the repository and workflow filename exactly, and that the workflow has `id-token: write`.
260
320
 
@@ -4,8 +4,10 @@
4
4
  ".agents/README.md",
5
5
  ".agents/adapters/claude/README.md",
6
6
  ".agents/adapters/codex/README.md",
7
+ ".agents/adapters/manifest.schema.json",
7
8
  ".agents/adapters/opencode/README.md",
8
9
  ".agents/adapters/pi/README.md",
10
+ ".agents/adapters/spec-kit/adapter.json",
9
11
  ".agents/common/README.md",
10
12
  ".agents/common/log-safety.js",
11
13
  ".agents/eval-fixtures/skill-output/invalid/missing-evidence/output.json",
@@ -14,6 +16,7 @@
14
16
  ".agents/fixtures/linear/issue-snapshot.json",
15
17
  ".agents/hooks/README.md",
16
18
  ".agents/hooks/bash-worktree-fix.sh",
19
+ ".agents/hooks/codex-session-status.js",
17
20
  ".agents/hooks/post-tool-logger.js",
18
21
  ".agents/hooks/session-tracker.js",
19
22
  ".agents/hooks/user-prompt-logger.js",
@@ -87,8 +90,10 @@
87
90
  ".agents/scripts/pm/epic-list.sh",
88
91
  ".agents/scripts/pm/in-progress.sh",
89
92
  ".agents/scripts/pm/init.sh",
93
+ ".agents/scripts/pm/import-spec-kit.sh",
90
94
  ".agents/scripts/pm/next.sh",
91
95
  ".agents/scripts/pm/prd-list.sh",
96
+ ".agents/scripts/pm/research.sh",
92
97
  ".agents/scripts/pm/search.sh",
93
98
  ".agents/scripts/pm/standup.sh",
94
99
  ".agents/scripts/pm/status.sh",
@@ -155,6 +160,7 @@
155
160
  ".agents/validation-fixtures/strict/invalid/stale-context/context.md",
156
161
  ".agents/validation-fixtures/strict/manifest.json",
157
162
  ".agents/validation-fixtures/strict/valid/minimal-project/task.md",
163
+ ".codex/hooks.json",
158
164
  ".delano/README.md",
159
165
  ".delano/viewer/README.md",
160
166
  ".delano/viewer/server.js",
@@ -168,6 +174,7 @@
168
174
  ".project/registry/linear-map.json",
169
175
  ".project/registry/migration-map.json",
170
176
  ".project/templates/completion-summary.md",
177
+ ".project/templates/decisions.md",
171
178
  ".project/templates/plan.md",
172
179
  ".project/templates/progress-update.md",
173
180
  ".project/templates/spec.md",
@@ -0,0 +1,103 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://delano.dev/schemas/adapter-manifest.schema.json",
4
+ "title": "Delano Adapter Manifest",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "id",
9
+ "name",
10
+ "type",
11
+ "owner",
12
+ "status",
13
+ "summary",
14
+ "commands",
15
+ "generated_files",
16
+ "validation",
17
+ "install",
18
+ "limits"
19
+ ],
20
+ "properties": {
21
+ "id": {
22
+ "type": "string",
23
+ "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
24
+ },
25
+ "name": {
26
+ "type": "string",
27
+ "minLength": 1
28
+ },
29
+ "type": {
30
+ "type": "string",
31
+ "enum": ["agent", "authoring-tool", "sync-tool", "workflow"]
32
+ },
33
+ "owner": {
34
+ "type": "string",
35
+ "minLength": 1
36
+ },
37
+ "status": {
38
+ "type": "string",
39
+ "enum": ["proposed", "experimental", "stable", "deprecated"]
40
+ },
41
+ "summary": {
42
+ "type": "string",
43
+ "minLength": 1
44
+ },
45
+ "commands": {
46
+ "type": "array",
47
+ "minItems": 1,
48
+ "items": {
49
+ "type": "object",
50
+ "additionalProperties": false,
51
+ "required": ["name", "description", "input", "output", "writes", "validation"],
52
+ "properties": {
53
+ "name": { "type": "string", "minLength": 1 },
54
+ "description": { "type": "string", "minLength": 1 },
55
+ "input": { "type": "array", "items": { "type": "string" } },
56
+ "output": { "type": "array", "items": { "type": "string" } },
57
+ "writes": { "type": "array", "items": { "type": "string" } },
58
+ "validation": { "type": "array", "items": { "type": "string" } }
59
+ }
60
+ }
61
+ },
62
+ "generated_files": {
63
+ "type": "array",
64
+ "items": {
65
+ "type": "object",
66
+ "additionalProperties": false,
67
+ "required": ["path", "owner", "mode", "conflict_behavior", "fold_forward"],
68
+ "properties": {
69
+ "path": { "type": "string", "minLength": 1 },
70
+ "owner": { "type": "string", "minLength": 1 },
71
+ "mode": { "type": "string", "enum": ["create-only", "update-owned", "proposal-only", "never-overwrite"] },
72
+ "conflict_behavior": { "type": "string", "enum": ["abort", "diff-required", "operator-approval-required"] },
73
+ "fold_forward": { "type": "string", "minLength": 1 }
74
+ }
75
+ }
76
+ },
77
+ "validation": {
78
+ "type": "array",
79
+ "minItems": 1,
80
+ "items": { "type": "string" }
81
+ },
82
+ "install": {
83
+ "type": "object",
84
+ "additionalProperties": false,
85
+ "required": ["categories", "conflict_policy"],
86
+ "properties": {
87
+ "categories": {
88
+ "type": "array",
89
+ "items": { "type": "string" }
90
+ },
91
+ "conflict_policy": {
92
+ "type": "string",
93
+ "minLength": 1
94
+ }
95
+ }
96
+ },
97
+ "limits": {
98
+ "type": "array",
99
+ "minItems": 1,
100
+ "items": { "type": "string" }
101
+ }
102
+ }
103
+ }
@@ -0,0 +1,71 @@
1
+ {
2
+ "id": "spec-kit",
3
+ "name": "Spec Kit Interop",
4
+ "type": "authoring-tool",
5
+ "owner": "delano-team",
6
+ "status": "experimental",
7
+ "summary": "Imports Spec Kit-style intent artifacts into Delano-governed delivery projects.",
8
+ "commands": [
9
+ {
10
+ "name": "delano import-spec-kit",
11
+ "description": "Create a planned Delano project from a supported Spec Kit-style markdown artifact.",
12
+ "input": ["slug", "source-md", "--name", "--owner", "--lead", "--json"],
13
+ "output": ["human summary", "JSON result with ok, command, project, source, validation"],
14
+ "writes": [".project/projects/<slug>/"],
15
+ "validation": ["delano validate"]
16
+ },
17
+ {
18
+ "name": "delano research",
19
+ "description": "Open repo-native research intake for unclear imported intent.",
20
+ "input": ["project-slug", "research-slug", "--title", "--question", "--json"],
21
+ "output": ["human summary", "JSON result with ok, command, project, research, files, validation"],
22
+ "writes": [".project/projects/<project-slug>/research/<research-slug>/"],
23
+ "validation": ["delano validate"]
24
+ }
25
+ ],
26
+ "generated_files": [
27
+ {
28
+ "path": ".project/projects/<slug>/spec.md",
29
+ "owner": "spec-kit adapter",
30
+ "mode": "create-only",
31
+ "conflict_behavior": "abort",
32
+ "fold_forward": "canonical spec"
33
+ },
34
+ {
35
+ "path": ".project/projects/<slug>/plan.md",
36
+ "owner": "spec-kit adapter",
37
+ "mode": "create-only",
38
+ "conflict_behavior": "abort",
39
+ "fold_forward": "canonical plan"
40
+ },
41
+ {
42
+ "path": ".project/projects/<slug>/tasks/*.md",
43
+ "owner": "spec-kit adapter",
44
+ "mode": "create-only",
45
+ "conflict_behavior": "abort",
46
+ "fold_forward": "canonical tasks with evidence gates"
47
+ },
48
+ {
49
+ "path": ".project/projects/<project-slug>/research/<research-slug>/",
50
+ "owner": "research intake",
51
+ "mode": "create-only",
52
+ "conflict_behavior": "abort",
53
+ "fold_forward": "spec, plan, decisions, workstreams, tasks, or updates"
54
+ }
55
+ ],
56
+ "validation": [
57
+ "delano validate",
58
+ "npm run check:text-safety for Delano repo changes",
59
+ "fixture import smoke before release"
60
+ ],
61
+ "install": {
62
+ "categories": ["agent-runtime", "project-templates", "skills"],
63
+ "conflict_policy": "Use existing Delano install allowlist behavior; abort on generated project collisions unless an operator approves a diff-backed change."
64
+ },
65
+ "limits": [
66
+ "Does not replace Spec Kit.",
67
+ "Does not execute imported tasks automatically.",
68
+ "Does not write Linear or GitHub state directly.",
69
+ "Does not depend on Obsidian, OpenClaw, or private local paths."
70
+ ]
71
+ }
@@ -7,5 +7,10 @@ Default hooks:
7
7
  - `post-tool-logger.js`
8
8
  - `user-prompt-logger.js`
9
9
  - `bash-worktree-fix.sh`
10
+ - `codex-session-status.js`
10
11
 
11
- All hooks append JSONL records in `.agents/logs/`.
12
+ The logging hooks append JSONL records in `.agents/logs/`.
13
+
14
+ `codex-session-status.js` is used by the optional `.codex/hooks.json` SessionStart
15
+ configuration. It emits `delano status --open --brief` context and fails open if
16
+ the local runtime is not available.
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env node
2
+ const { existsSync } = require("node:fs");
3
+ const path = require("node:path");
4
+ const { spawnSync } = require("node:child_process");
5
+
6
+ function findDelanoRoot(startDir) {
7
+ let current = path.resolve(startDir);
8
+ while (true) {
9
+ if (
10
+ existsSync(path.join(current, ".project", "projects")) &&
11
+ existsSync(path.join(current, ".agents", "scripts", "pm", "status.sh"))
12
+ ) {
13
+ return current;
14
+ }
15
+
16
+ const parent = path.dirname(current);
17
+ if (parent === current) {
18
+ return null;
19
+ }
20
+ current = parent;
21
+ }
22
+ }
23
+
24
+ function toBashPath(filePath) {
25
+ return filePath.replace(/\\/g, "/");
26
+ }
27
+
28
+ function resolveBash() {
29
+ const candidates = [];
30
+
31
+ if (process.env.DELANO_BASH) {
32
+ candidates.push(process.env.DELANO_BASH);
33
+ }
34
+
35
+ if (process.platform === "win32") {
36
+ candidates.push(
37
+ "C:\\Program Files\\Git\\bin\\bash.exe",
38
+ "C:\\Program Files\\Git\\usr\\bin\\bash.exe"
39
+ );
40
+
41
+ const whereResult = spawnSync("where.exe", ["bash"], {
42
+ encoding: "utf8",
43
+ stdio: ["ignore", "pipe", "ignore"]
44
+ });
45
+ if (whereResult.status === 0 && whereResult.stdout) {
46
+ candidates.push(...whereResult.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean));
47
+ }
48
+ } else {
49
+ const whichResult = spawnSync("which", ["bash"], {
50
+ encoding: "utf8",
51
+ stdio: ["ignore", "pipe", "ignore"]
52
+ });
53
+ if (whichResult.status === 0 && whichResult.stdout.trim()) {
54
+ candidates.push(whichResult.stdout.trim());
55
+ }
56
+ candidates.push("/usr/bin/bash", "/bin/bash");
57
+ }
58
+
59
+ return candidates.find((candidate) => candidate && existsSync(candidate)) || null;
60
+ }
61
+
62
+ const root = findDelanoRoot(process.cwd());
63
+ const bashPath = resolveBash();
64
+ if (!root || !bashPath) {
65
+ process.exit(0);
66
+ }
67
+
68
+ const statusScript = toBashPath(path.join(root, ".agents", "scripts", "pm", "status.sh"));
69
+ const result = spawnSync(bashPath, [statusScript, "--open", "--brief"], {
70
+ cwd: root,
71
+ encoding: "utf8",
72
+ timeout: 4500,
73
+ maxBuffer: 64 * 1024
74
+ });
75
+
76
+ if (result.error || result.status !== 0) {
77
+ process.exit(0);
78
+ }
79
+
80
+ const statusOutput = result.stdout.trim();
81
+ if (!statusOutput) {
82
+ process.exit(0);
83
+ }
84
+
85
+ const additionalContext = formatStatusContext(statusOutput);
86
+
87
+ console.log(JSON.stringify({
88
+ hookSpecificOutput: {
89
+ hookEventName: "SessionStart",
90
+ additionalContext
91
+ }
92
+ }));
93
+
94
+ function formatStatusContext(rawStatusOutput) {
95
+ const lines = rawStatusOutput.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
96
+ const projectLines = lines.filter((line) => (
97
+ !line.startsWith("Delano ") &&
98
+ !/^=+$/.test(line) &&
99
+ !line.startsWith("No open projects")
100
+ ));
101
+
102
+ if (projectLines.length === 0) {
103
+ return "Delano startup context. Open projects: none.";
104
+ }
105
+
106
+ const projects = projectLines.map(formatProjectLine);
107
+ return `Delano startup context. Open projects: ${projects.join("; ")}.`;
108
+ }
109
+
110
+ function formatProjectLine(line) {
111
+ const match = line.match(/^(\S+)\s+spec=(\S+)\s+plan=(\S+)\s+open_tasks=(\d+)\s+total_tasks=(\d+)$/);
112
+ if (!match) {
113
+ return line;
114
+ }
115
+
116
+ const [, slug, spec, plan, openTasks, totalTasks] = match;
117
+ return `${slug} (spec=${spec}, plan=${plan}, open_tasks=${openTasks}, total_tasks=${totalTasks})`;
118
+ }
119
+
120
+ module.exports = {
121
+ formatProjectLine,
122
+ formatStatusContext
123
+ };
@@ -21,6 +21,41 @@
21
21
  "description": "A done task must not close over unresolved local dependencies.",
22
22
  "requires": ["all local depends_on task statuses are done"]
23
23
  },
24
+ {
25
+ "id": "progressed-task-requires-active-project",
26
+ "status": "in-progress|done",
27
+ "description": "A task must not start or complete while the parent project spec or plan is still planned.",
28
+ "requires": [
29
+ "spec.status is active or complete",
30
+ "plan.status is active or done"
31
+ ]
32
+ },
33
+ {
34
+ "id": "closed-task-set-requires-closed-project",
35
+ "status": "done|deferred",
36
+ "description": "A project with no open tasks must not remain open through stale spec or plan statuses.",
37
+ "requires": [
38
+ "spec.status is complete or deferred",
39
+ "plan.status is done or deferred"
40
+ ]
41
+ },
42
+ {
43
+ "id": "progressed-task-requires-active-workstream",
44
+ "status": "in-progress|done",
45
+ "description": "A task must not start or complete before its parent workstream has started.",
46
+ "requires": [
47
+ "in-progress tasks require workstream.status active",
48
+ "done tasks require workstream.status active or done"
49
+ ]
50
+ },
51
+ {
52
+ "id": "closed-task-set-requires-closed-workstream",
53
+ "status": "done|deferred",
54
+ "description": "A workstream with no open tasks must not remain open through a stale status.",
55
+ "requires": [
56
+ "workstream.status is done or deferred"
57
+ ]
58
+ },
24
59
  {
25
60
  "id": "blocked-owner-check-back",
26
61
  "status": "blocked",
@@ -11,7 +11,7 @@ Compatibility path: `.claude/scripts/...` when the mirror is present.
11
11
  Critical path:
12
12
  - `init.sh`
13
13
  - `validate.sh`
14
- - `status.sh`
14
+ - `status.sh` (`--open` and `--brief` are available for compact startup context)
15
15
  - `next.sh`
16
16
  - `blocked.sh`
17
17