@bvdm/delano 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -79
- package/assets/payload/__dot_gitignore__ +20 -0
- package/package.json +1 -1
- package/src/cli/commands/install.js +7 -1
- package/src/cli/index.js +56 -13
- package/src/cli/lib/asset-paths.js +21 -0
- package/src/cli/lib/install.js +13 -6
- package/src/cli/lib/runtime.js +16 -6
package/README.md
CHANGED
|
@@ -1,122 +1,124 @@
|
|
|
1
1
|
# Delano
|
|
2
2
|
|
|
3
|
-
Delano is an agent-agnostic
|
|
3
|
+
Delano is an agent-agnostic delivery runtime. It keeps planning, execution, and evidence on disk so teams can work with different coding agents without changing the operating model every time.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## What Delano is
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- `HANDBOOK.md` is the canonical operating model.
|
|
8
|
+
- `.project/` is the delivery source of truth.
|
|
9
|
+
- `.agents/` is the shared runtime: scripts, rules, hooks, skills, and adapters.
|
|
10
|
+
- `.claude/` is a compatibility mirror of `.agents/`, not a second runtime.
|
|
11
|
+
- `.delano/` is an optional UI layer.
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
The npm package is intentionally thin. It distributes the approved runtime payload and wraps the existing shell-based PM scripts. It does not replace the handbook, the file contracts, or the underlying bash/Python execution layer.
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
- execution drift between planning and implementation
|
|
13
|
-
- tool-specific lock-in
|
|
14
|
-
- undocumented decisions and low-trust delivery flow
|
|
15
|
+
## Delano CLI
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
- Package: `@bvdm/delano`
|
|
18
|
+
- Binary: `delano`
|
|
19
|
+
- Commands: `install`, `init`, `validate`, `status`, `next`
|
|
20
|
+
- Primary v1.1 goal: bootstrap a repo safely, then stay out of the way
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
## One-command bootstrap
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
- `HANDBOOK.md` — canonical operating model and governance
|
|
22
|
-
- `.project/` — delivery truth (projects, tasks, context, updates, decisions)
|
|
23
|
-
- `.agents/` — shared runtime (scripts, rules, hooks, skills, adapters)
|
|
24
|
-
- `.claude/` — compatibility mirror for Claude-style paths (symlink where supported, directory mirror otherwise)
|
|
25
|
-
- `.delano/` — optional UI layer
|
|
24
|
+
To install the approved Delano runtime into the current repository:
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
```bash
|
|
27
|
+
npx -y @bvdm/delano@latest --yes
|
|
28
|
+
```
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
- Claude Code
|
|
31
|
-
- Codex CLI
|
|
32
|
-
- OpenCode
|
|
33
|
-
- Pi coding agent
|
|
30
|
+
That shorthand is equivalent to:
|
|
34
31
|
|
|
35
|
-
|
|
32
|
+
```bash
|
|
33
|
+
npx -y @bvdm/delano@latest install --yes
|
|
34
|
+
```
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
To install into a different directory:
|
|
38
37
|
|
|
39
38
|
```bash
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
# 2) Create a new delivery project scaffold
|
|
44
|
-
bash .agents/scripts/pm/init.sh <slug> "<Project Name>" <owner> <lead>
|
|
39
|
+
npx -y @bvdm/delano@latest --target /path/to/repo --yes
|
|
40
|
+
```
|
|
45
41
|
|
|
46
|
-
|
|
47
|
-
bash .agents/scripts/pm/status.sh
|
|
42
|
+
If you already have the package installed locally, the same flow is:
|
|
48
43
|
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
```bash
|
|
45
|
+
delano --yes
|
|
46
|
+
delano --target /path/to/repo --yes
|
|
51
47
|
```
|
|
52
48
|
|
|
53
|
-
|
|
49
|
+
## How to use Delano after install
|
|
54
50
|
|
|
55
|
-
|
|
56
|
-
- package name `@bvdm/delano`
|
|
57
|
-
- binary name `delano`
|
|
58
|
-
- embedded install assets instead of a GitHub-fetch wrapper
|
|
59
|
-
- wrapper commands for `install`, `init`, `validate`, `status`, and `next`
|
|
51
|
+
If you bootstrap with one-shot `npx`, keep using `npx` for wrapper commands:
|
|
60
52
|
|
|
61
|
-
|
|
53
|
+
```bash
|
|
54
|
+
npx -y @bvdm/delano@latest validate
|
|
55
|
+
npx -y @bvdm/delano@latest status
|
|
56
|
+
npx -y @bvdm/delano@latest next -- --all
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
If the package is installed locally or globally, run these inside the target repository:
|
|
62
60
|
|
|
63
61
|
```bash
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
delano validate
|
|
63
|
+
delano status
|
|
64
|
+
delano next -- --all
|
|
65
|
+
delano init <slug> "<Project Name>" <owner> <lead>
|
|
66
|
+
```
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
node bin/delano.js --help
|
|
68
|
+
The wrapper commands call the existing PM scripts under `.agents/scripts/pm/`. You can also run those scripts directly:
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
```bash
|
|
71
|
+
bash .agents/scripts/pm/validate.sh
|
|
72
|
+
bash .agents/scripts/pm/status.sh
|
|
73
|
+
bash .agents/scripts/pm/next.sh --all
|
|
72
74
|
```
|
|
73
75
|
|
|
74
|
-
|
|
76
|
+
## Required dependencies
|
|
75
77
|
|
|
76
|
-
1
|
|
77
|
-
2. Execute work through workstreams and atomic tasks (`tasks/T-xxx.md`).
|
|
78
|
-
3. Record progress/blockers in `updates/*.md`.
|
|
79
|
-
4. Re-run validation before merge/handoff:
|
|
80
|
-
```bash
|
|
81
|
-
bash .agents/scripts/pm/validate.sh
|
|
82
|
-
```
|
|
78
|
+
Delano v1.1 assumes these tools are available:
|
|
83
79
|
|
|
84
|
-
|
|
80
|
+
- `node` 18 or newer
|
|
81
|
+
- `bash`
|
|
82
|
+
- `git`
|
|
83
|
+
- `python3` or compatible `python` / `py`
|
|
85
84
|
|
|
86
|
-
|
|
85
|
+
The CLI does not bundle its own shell or Python runtime.
|
|
87
86
|
|
|
88
|
-
|
|
89
|
-
delano install --target /path/to/repo --yes
|
|
90
|
-
```
|
|
87
|
+
## Install behavior
|
|
91
88
|
|
|
92
|
-
|
|
89
|
+
`delano install` is conflict-first by default:
|
|
93
90
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
- it computes the full install plan before writing files
|
|
92
|
+
- it aborts if an approved target path already exists
|
|
93
|
+
- it reports each conflict as file, directory, or symlink
|
|
94
|
+
- it only overwrites approved allowlist paths when `--force` is used
|
|
95
|
+
- it does not touch unrelated files outside the allowlist
|
|
97
96
|
|
|
98
|
-
|
|
99
|
-
- installs only the approved allowlist payload
|
|
100
|
-
- aborts on existing-path conflicts unless `--force` is used
|
|
101
|
-
- does not install `AGENTS.md`, `CLAUDE.md`, `CODEX.md`, `OPENCODE.md`, or `PI.md` by default
|
|
97
|
+
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.
|
|
102
98
|
|
|
103
|
-
|
|
99
|
+
## v1.1 boundaries
|
|
104
100
|
|
|
105
|
-
|
|
106
|
-
./install-delano.sh
|
|
107
|
-
```
|
|
101
|
+
This package is deliberately narrow:
|
|
108
102
|
|
|
109
|
-
|
|
103
|
+
- npm is the distribution surface
|
|
104
|
+
- `.project` remains repo-owned after install
|
|
105
|
+
- `.agents` remains the runtime surface
|
|
106
|
+
- wrapper commands stay thin in v1.1
|
|
107
|
+
- `install-delano.sh` remains available as the legacy bridge installer
|
|
110
108
|
|
|
111
|
-
|
|
109
|
+
## Local development
|
|
110
|
+
|
|
111
|
+
From this repository:
|
|
112
112
|
|
|
113
113
|
```bash
|
|
114
|
-
|
|
114
|
+
npm run build:assets
|
|
115
|
+
node bin/delano.js --help
|
|
116
|
+
node bin/delano.js --yes --target ./tmp/cli-install-smoke
|
|
115
117
|
```
|
|
116
118
|
|
|
117
|
-
|
|
119
|
+
## Read next
|
|
118
120
|
|
|
119
|
-
- `docs/user-guide.md` for the user
|
|
120
|
-
- `HANDBOOK.md` for full operating
|
|
121
|
-
- `.agents/scripts/README.md` for runtime script inventory
|
|
122
|
-
- `AGENTS.md`
|
|
121
|
+
- `docs/user-guide.md` for the practical user flow
|
|
122
|
+
- `HANDBOOK.md` for the full operating model
|
|
123
|
+
- `.agents/scripts/README.md` for the runtime script inventory
|
|
124
|
+
- `AGENTS.md` for adapter-neutral instructions
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Runtime local artifacts
|
|
2
|
+
.claude/logs/*.jsonl
|
|
3
|
+
.claude/logs/tests/
|
|
4
|
+
.agents/logs/*.jsonl
|
|
5
|
+
.agents/logs/tests/
|
|
6
|
+
|
|
7
|
+
# Keep structural markers and docs
|
|
8
|
+
!.claude/logs/.gitkeep
|
|
9
|
+
!.claude/logs/schema.md
|
|
10
|
+
!.agents/logs/.gitkeep
|
|
11
|
+
!.agents/logs/schema.md
|
|
12
|
+
|
|
13
|
+
# Local Claude overrides
|
|
14
|
+
.claude/settings.local.json
|
|
15
|
+
.agents/settings.local.json
|
|
16
|
+
|
|
17
|
+
# Node/npm local artifacts
|
|
18
|
+
node_modules/
|
|
19
|
+
assets/payload/
|
|
20
|
+
*.tgz
|
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@ function getInstallHelp() {
|
|
|
12
12
|
return [
|
|
13
13
|
"Usage:",
|
|
14
14
|
" delano install [options]",
|
|
15
|
+
" delano [options]",
|
|
15
16
|
"",
|
|
16
17
|
"Options:",
|
|
17
18
|
" --target <dir> Install into the given directory. Defaults to the current working directory.",
|
|
@@ -23,7 +24,12 @@ function getInstallHelp() {
|
|
|
23
24
|
"Behavior:",
|
|
24
25
|
" - Computes the full install plan before writing files.",
|
|
25
26
|
" - Aborts on conflicts by default.",
|
|
26
|
-
" - Only installs the approved base payload; top-level adapter entry docs remain opt-in and are not installed in v1."
|
|
27
|
+
" - Only installs the approved base payload; top-level adapter entry docs remain opt-in and are not installed in v1.",
|
|
28
|
+
"",
|
|
29
|
+
"Examples:",
|
|
30
|
+
" delano install --target ../repo --yes",
|
|
31
|
+
" delano --yes",
|
|
32
|
+
" npx -y @bvdm/delano@latest --yes"
|
|
27
33
|
].join("\n");
|
|
28
34
|
}
|
|
29
35
|
|
package/src/cli/index.js
CHANGED
|
@@ -25,6 +25,51 @@ const commands = {
|
|
|
25
25
|
next: wrapperCommands.next
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
function isInstallShorthand(argv) {
|
|
29
|
+
if (argv.length === 0) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return argv[0].startsWith("-");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function resolveInvocation(argv) {
|
|
37
|
+
if (argv.length === 0) {
|
|
38
|
+
return { kind: "help" };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const [commandName, ...commandArgs] = argv;
|
|
42
|
+
|
|
43
|
+
if (commandName === "-h" || commandName === "--help" || commandName === "help") {
|
|
44
|
+
return { kind: "help" };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (commandName === "-v" || commandName === "--version" || commandName === "version") {
|
|
48
|
+
return { kind: "version" };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (isInstallShorthand(argv)) {
|
|
52
|
+
return {
|
|
53
|
+
kind: "command",
|
|
54
|
+
commandName: "install",
|
|
55
|
+
commandArgs: argv,
|
|
56
|
+
command: commands.install
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const command = commands[commandName];
|
|
61
|
+
if (!command) {
|
|
62
|
+
throw new CliError(`Unknown command: ${commandName}\n\n${getGeneralHelp()}`, 1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
kind: "command",
|
|
67
|
+
commandName,
|
|
68
|
+
commandArgs,
|
|
69
|
+
command
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
28
73
|
function getPackageVersion() {
|
|
29
74
|
const packageJsonPath = path.join(getPackageRoot(), "package.json");
|
|
30
75
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
@@ -50,36 +95,33 @@ function getGeneralHelp() {
|
|
|
50
95
|
" -v, --version Show version",
|
|
51
96
|
"",
|
|
52
97
|
"Examples:",
|
|
53
|
-
" delano
|
|
98
|
+
" delano --yes",
|
|
99
|
+
" delano --target ../my-repo --yes",
|
|
100
|
+
" npx -y @bvdm/delano@latest --yes",
|
|
54
101
|
" delano validate",
|
|
55
102
|
" delano next -- --all",
|
|
56
103
|
"",
|
|
104
|
+
"Shorthand:",
|
|
105
|
+
" delano [install-options] is equivalent to delano install [install-options].",
|
|
106
|
+
"",
|
|
57
107
|
"Use 'delano <command> --help' for command-specific help."
|
|
58
108
|
].join("\n");
|
|
59
109
|
}
|
|
60
110
|
|
|
61
111
|
async function run(argv) {
|
|
62
|
-
|
|
63
|
-
console.log(getGeneralHelp());
|
|
64
|
-
return 0;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const [commandName, ...commandArgs] = argv;
|
|
112
|
+
const invocation = resolveInvocation(argv);
|
|
68
113
|
|
|
69
|
-
if (
|
|
114
|
+
if (invocation.kind === "help") {
|
|
70
115
|
console.log(getGeneralHelp());
|
|
71
116
|
return 0;
|
|
72
117
|
}
|
|
73
118
|
|
|
74
|
-
if (
|
|
119
|
+
if (invocation.kind === "version") {
|
|
75
120
|
console.log(getPackageVersion());
|
|
76
121
|
return 0;
|
|
77
122
|
}
|
|
78
123
|
|
|
79
|
-
const command =
|
|
80
|
-
if (!command) {
|
|
81
|
-
throw new CliError(`Unknown command: ${commandName}\n\n${getGeneralHelp()}`, 1);
|
|
82
|
-
}
|
|
124
|
+
const { command, commandArgs } = invocation;
|
|
83
125
|
|
|
84
126
|
if (commandArgs.includes("--help") || commandArgs.includes("-h")) {
|
|
85
127
|
const helpText = typeof command.help === "function" ? command.help() : getGeneralHelp();
|
|
@@ -93,5 +135,6 @@ async function run(argv) {
|
|
|
93
135
|
module.exports = {
|
|
94
136
|
commands,
|
|
95
137
|
getGeneralHelp,
|
|
138
|
+
resolveInvocation,
|
|
96
139
|
run
|
|
97
140
|
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const path = require("node:path");
|
|
2
|
+
|
|
3
|
+
const PACKAGED_BASENAME_MAP = new Map([
|
|
4
|
+
[".gitignore", "__dot_gitignore__"]
|
|
5
|
+
]);
|
|
6
|
+
|
|
7
|
+
function getPackagedAssetRelativePath(relativePath) {
|
|
8
|
+
const normalizedPath = relativePath.split(/[\\/]/).join("/");
|
|
9
|
+
const parsedPath = path.posix.parse(normalizedPath);
|
|
10
|
+
const packagedBasename = PACKAGED_BASENAME_MAP.get(parsedPath.base);
|
|
11
|
+
|
|
12
|
+
if (!packagedBasename) {
|
|
13
|
+
return normalizedPath;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return parsedPath.dir ? `${parsedPath.dir}/${packagedBasename}` : packagedBasename;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
getPackagedAssetRelativePath
|
|
21
|
+
};
|
package/src/cli/lib/install.js
CHANGED
|
@@ -11,11 +11,20 @@ const path = require("node:path");
|
|
|
11
11
|
const readline = require("node:readline/promises");
|
|
12
12
|
const { stdin, stdout } = require("node:process");
|
|
13
13
|
|
|
14
|
+
const { getPackagedAssetRelativePath } = require("./asset-paths");
|
|
14
15
|
const { CliError } = require("./errors");
|
|
15
16
|
const { getPackageRoot, getPathType } = require("./runtime");
|
|
16
17
|
|
|
17
18
|
const SUPPORTED_AGENTS = ["claude", "codex", "opencode", "pi"];
|
|
18
19
|
|
|
20
|
+
function getMissingPackagedAssetMessage(relativePath) {
|
|
21
|
+
return [
|
|
22
|
+
`Packaged asset missing for '${relativePath}'.`,
|
|
23
|
+
"If you are running from a source checkout, run 'npm run build:assets' first.",
|
|
24
|
+
"If you installed the published npm package, the package is incomplete and needs to be rebuilt and republished."
|
|
25
|
+
].join(" ");
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
function readInstallManifest() {
|
|
20
29
|
const manifestPath = path.join(getPackageRoot(), "assets", "install-manifest.json");
|
|
21
30
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
@@ -114,12 +123,9 @@ function parseInstallArgs(args) {
|
|
|
114
123
|
function buildInstallPlan(options) {
|
|
115
124
|
const { manifest, payloadRoot } = readInstallManifest();
|
|
116
125
|
const items = manifest.paths.map((relativePath) => {
|
|
117
|
-
const sourcePath = path.join(payloadRoot, relativePath);
|
|
126
|
+
const sourcePath = path.join(payloadRoot, getPackagedAssetRelativePath(relativePath));
|
|
118
127
|
if (!existsSync(sourcePath)) {
|
|
119
|
-
throw new CliError(
|
|
120
|
-
`Packaged asset missing for '${relativePath}'. Run 'npm run build:assets' before using 'delano install' from a source checkout.`,
|
|
121
|
-
1
|
|
122
|
-
);
|
|
128
|
+
throw new CliError(getMissingPackagedAssetMessage(relativePath), 1);
|
|
123
129
|
}
|
|
124
130
|
|
|
125
131
|
return {
|
|
@@ -257,5 +263,6 @@ module.exports = {
|
|
|
257
263
|
parseInstallArgs,
|
|
258
264
|
printConflicts,
|
|
259
265
|
printPlanSummary,
|
|
260
|
-
readInstallManifest
|
|
266
|
+
readInstallManifest,
|
|
267
|
+
getMissingPackagedAssetMessage
|
|
261
268
|
};
|
package/src/cli/lib/runtime.js
CHANGED
|
@@ -50,6 +50,11 @@ function resolveBash() {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
if (process.platform === "win32") {
|
|
53
|
+
candidates.push(
|
|
54
|
+
"C:\\Program Files\\Git\\bin\\bash.exe",
|
|
55
|
+
"C:\\Program Files\\Git\\usr\\bin\\bash.exe"
|
|
56
|
+
);
|
|
57
|
+
|
|
53
58
|
const whereResult = spawnSync("where.exe", ["bash"], {
|
|
54
59
|
encoding: "utf8",
|
|
55
60
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -62,11 +67,6 @@ function resolveBash() {
|
|
|
62
67
|
}
|
|
63
68
|
}
|
|
64
69
|
}
|
|
65
|
-
|
|
66
|
-
candidates.push(
|
|
67
|
-
"C:\\Program Files\\Git\\usr\\bin\\bash.exe",
|
|
68
|
-
"C:\\Program Files\\Git\\bin\\bash.exe"
|
|
69
|
-
);
|
|
70
70
|
} else {
|
|
71
71
|
const whichResult = spawnSync("which", ["bash"], {
|
|
72
72
|
encoding: "utf8",
|
|
@@ -90,9 +90,18 @@ function resolveBash() {
|
|
|
90
90
|
);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
function normalizeBashScriptPath(scriptPath) {
|
|
94
|
+
if (process.platform !== "win32") {
|
|
95
|
+
return scriptPath;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return scriptPath.replace(/\\/g, "/");
|
|
99
|
+
}
|
|
100
|
+
|
|
93
101
|
function runBashScript(scriptPath, args, options = {}) {
|
|
94
102
|
const bashPath = resolveBash();
|
|
95
|
-
const
|
|
103
|
+
const normalizedScriptPath = normalizeBashScriptPath(scriptPath);
|
|
104
|
+
const result = spawnSync(bashPath, [normalizedScriptPath, ...args], {
|
|
96
105
|
cwd: options.cwd || process.cwd(),
|
|
97
106
|
stdio: "inherit",
|
|
98
107
|
env: options.env || process.env
|
|
@@ -117,6 +126,7 @@ module.exports = {
|
|
|
117
126
|
findDelanoRoot,
|
|
118
127
|
getPackageRoot,
|
|
119
128
|
getPathType,
|
|
129
|
+
normalizeBashScriptPath,
|
|
120
130
|
resolveBash,
|
|
121
131
|
runBashScript
|
|
122
132
|
};
|