muse-crew 0.11.0 → 0.11.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/docs/state-boundary-spec.md +41 -0
- package/lib/AGENTS.md +2 -1
- package/lib/commit-scaffold.js +121 -0
- package/lib/setup-project-repo.js +57 -11
- package/package.json +1 -1
- package/workflows/crew-init.js +63 -58
|
@@ -141,3 +141,44 @@ New `tests/gitignore.test.js` and `tests/repo-orchestration.test.js`:
|
|
|
141
141
|
no approval to change).
|
|
142
142
|
- Does not add new API endpoints.
|
|
143
143
|
- Does not change Room #12 or any existing crew's behavior.
|
|
144
|
+
|
|
145
|
+
## 8. Setup commits its scaffold (0.11.1)
|
|
146
|
+
|
|
147
|
+
**Defect (room #13, 2026-09-17):** 0.11.0's setup wrote `.orchestration/`
|
|
148
|
+
(and the consented `.gitignore` update) but never committed them. The
|
|
149
|
+
first task's Integrate then refused to merge onto the dirty main — twice,
|
|
150
|
+
deterministically. An uncommitted scaffold deterministically breaks the
|
|
151
|
+
first task after setup.
|
|
152
|
+
|
|
153
|
+
**Contract:** setup leaves the repo's main checkout clean. After the
|
|
154
|
+
scaffold + `.gitignore` steps, `lib/commit-scaffold.js` commits exactly
|
|
155
|
+
the crew's paths (`.orchestration/`, plus `.gitignore` when the setup step
|
|
156
|
+
changed it under consent). The commit message names what was staged,
|
|
157
|
+
computed from the index.
|
|
158
|
+
|
|
159
|
+
**Rules:**
|
|
160
|
+
- Only the crew's paths are ever staged. The user's unrelated changes
|
|
161
|
+
(tracked modifications, untracked files) are never swept in.
|
|
162
|
+
- Idempotent: nothing new staged → no commit, `{ committed: false,
|
|
163
|
+
reason: "nothing-to-commit" }`. Never an empty commit.
|
|
164
|
+
- Missing `.orchestration/` → throw (the scaffold step did not run).
|
|
165
|
+
- Commit failures (e.g. no git identity) throw loudly with git's stderr.
|
|
166
|
+
No identity is ever invented — the commit belongs to the user; the
|
|
167
|
+
caller configures identity and re-runs.
|
|
168
|
+
- With `--consent`, `.gitignore` must be clean before setup touches it:
|
|
169
|
+
a pre-existing dirty `.gitignore` fails setup loudly (refuse — never
|
|
170
|
+
sweep the user's edits into the setup commit). `.orchestration/` is
|
|
171
|
+
crew-owned: pre-existing uncommitted content there is committed, not
|
|
172
|
+
refused (this also repairs 0.11.0 rooms).
|
|
173
|
+
- `lib/setup-project-repo.js` (the composer) runs all three steps and
|
|
174
|
+
exits non-zero on any failure. crew-init's dashboard mode calls the
|
|
175
|
+
composer (one agent call, replacing the old two-call shape); a failed
|
|
176
|
+
commit is logged loudly and surfaced as `repoSetupWarning` in the init
|
|
177
|
+
summary, since the first task will fail Integrate until the scaffold
|
|
178
|
+
is committed.
|
|
179
|
+
|
|
180
|
+
**Tests:** `tests/commit-scaffold.test.js` (behavioral: clean tree after
|
|
181
|
+
setup, accurate message, idempotency, non-repo no-op, missing scaffold
|
|
182
|
+
throws, missing identity throws, composer refusal on dirty `.gitignore`,
|
|
183
|
+
declined consent leaves `.gitignore` untouched; structural: crew-init
|
|
184
|
+
calls the composer).
|
package/lib/AGENTS.md
CHANGED
|
@@ -29,5 +29,6 @@ Shell scripts for the crew's infrastructure. Called by workflow scripts, cron, a
|
|
|
29
29
|
- `update-watch.js` — deterministic automatic update watcher (2026-09-16, zero deps): `node update-watch.js --crew-home <path>` (missing arg → usage, exit 2; every other path exits 0). Watches the public npm registry (`npm view muse-crew version` pinned to `https://registry.npmjs.org/`) vs `crew-release.sh current` and files a `workflow: "upgrade"` task with `source: npm@<version>` when policy (`auto_update_crew`, `update_channel`) and channel gating allow; watches `git ls-remote origin HEAD` on the first `deploy_type=artifact` project vs `$CREW_HOME/.update-watch.json` and files a `workflow: "chore"` task carrying the mechanical dashboard-upgrade journey. Reads the `.crew-version` compatibility anchor at the new ref via `git fetch` + `git show <sha>:.crew-version` (never the working tree) and orders dashboard-led: a declared newer crew files the crew upgrade task FIRST and the dashboard task notes it follows the crew upgrade (declaration bypasses `update_channel`, not the `auto_update_crew=false` opt-out); a declared older crew skips the dashboard leg entirely as a human decision; a missing/invalid/unfetchable anchor fails open to the dashboard leg as today. Idempotency via the same state file (records at file time); check failures log to `$CREW_HOME/update-watch.log` and are never thrown. Safety: only files tasks — never deploys, never touches the artifact/config/scheduler. Run by the daily `crew-update-watch` cron through the `current` symlink (latest release); deliberately NOT in the lib-pinning `PIN_BASENAMES`.
|
|
30
30
|
- `gitignore.js` — deterministic .gitignore management for crew-owned paths (2026-09-17): the crew touches exactly one user-owned file outside `.orchestration/` — the repo's `.gitignore`. `ensureGitignoreEntries(repoPath, entries)` creates the file when missing, appends missing entries (exact line match, no duplicates), preserves existing content byte-for-byte, and is idempotent. `describeGitignoreChange(repoPath, entries)` renders the exact diff for the setup consent conversation. Crew-owned entries: `.worktrees/`, `.orchestration/user/`. CLI: `--repo <path> [--dry-run]`.
|
|
31
31
|
- `repo-orchestration.js` — repository-local `.orchestration/` scaffold (2026-09-17): `scaffoldRepoOrchestration(repoPath, crewRepoPath)` creates `$REPO/.orchestration/{workflows,identities,phases,user}/`, seeds workflows/identities/phases from the crew repo's platform defaults with no-clobber semantics (existing project customizations never overwritten), and writes a README in `user/` explaining it's for local config. Idempotent. CLI: `--repo <path> --crew-repo <path>`.
|
|
32
|
-
- `setup-project-repo.js` — repo-local setup composer for a registered project (2026-09-17): runs the `.orchestration/` scaffold plus the `.gitignore` update (only with explicit `--consent`). The CLI-path equivalent of crew-init's dashboard-mode repo setup; the dashboard calls the same script. No new API endpoint — composition of existing deterministic modules. CLI: `--repo <path> --crew-repo <path> [--consent]
|
|
32
|
+
- `setup-project-repo.js` — repo-local setup composer for a registered project (2026-09-17, setup-commit 2026-09-18): runs the `.orchestration/` scaffold plus the `.gitignore` update (only with explicit `--consent`), then COMMITS both via `commit-scaffold.js` so the repo's main checkout is clean when setup finishes (room #13: an uncommitted scaffold deterministically broke the first task's Integrate). Refuses loudly when `.gitignore` has pre-existing uncommitted changes (never sweeps the user's edits into the setup commit); `.orchestration/` is crew-owned so pre-existing content there is committed, not refused. The CLI-path equivalent of crew-init's dashboard-mode repo setup; the dashboard calls the same script. No new API endpoint — composition of existing deterministic modules. CLI: `--repo <path> --crew-repo <path> [--consent]`; prints `{ orchestration, gitignore, commit }`, exits non-zero on any failure.
|
|
33
|
+
- `commit-scaffold.js` — deterministic setup-scaffold commit (2026-09-18): stages only `.orchestration/` (+ `.gitignore` with `--gitignore`) and commits with a message naming exactly what was staged. Idempotent (`nothing-to-commit`, never an empty commit); not-a-git-repo is a clean no-op; missing `.orchestration/` and commit failures (e.g. no git identity) throw loudly — the commit belongs to the user, so no identity is ever invented here. Exports `isPathDirty` (porcelain check for one path) for the composer's pre-mutation guard. CLI: `--repo <path> [--gitignore]`.
|
|
33
34
|
- `sample-project.js` — optional sample-project setup (2026-09-17): extracts `seed/sample-project.tar.gz` (stripping the `sample-project/` prefix), `git init`, initial commit. No remote required. Registration happens via the existing `create-project` action — no sample-specific endpoint. CLI: `--tarball <path> --dest <dir> --name <project-name>`.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// lib/commit-scaffold.js — commit the crew's setup scaffold files.
|
|
3
|
+
//
|
|
4
|
+
// After project setup writes $REPO/.orchestration/ (and, with the user's
|
|
5
|
+
// consent, updates .gitignore), this module commits exactly those paths so
|
|
6
|
+
// the repo's main checkout is clean when setup finishes.
|
|
7
|
+
//
|
|
8
|
+
// Why this exists (room #13, 2026-09-17): 0.11.0's setup wrote the scaffold
|
|
9
|
+
// but never committed it. The first task's Integrate then refused to merge
|
|
10
|
+
// onto the dirty main — twice in a row, deterministically. An uncommitted
|
|
11
|
+
// scaffold deterministically breaks the first task after setup. Setup must
|
|
12
|
+
// leave main clean; this is the mechanical half of that contract.
|
|
13
|
+
//
|
|
14
|
+
// Contract:
|
|
15
|
+
// - Only ever stages `.orchestration/` and (with --gitignore) `.gitignore`.
|
|
16
|
+
// The user's unrelated changes are never swept into the setup commit.
|
|
17
|
+
// - Idempotent: nothing new staged → { committed: false, reason:
|
|
18
|
+
// "nothing-to-commit" }; never creates an empty commit.
|
|
19
|
+
// - Not a git repo → { committed: false, reason: "not-a-git-repo" }.
|
|
20
|
+
// There is no main to keep clean, so there is nothing to do.
|
|
21
|
+
// - Missing `.orchestration/` → throws. The scaffold step did not run;
|
|
22
|
+
// fail loudly, never silently skip.
|
|
23
|
+
// - Commit failures (e.g. no git identity configured) throw with git's
|
|
24
|
+
// stderr. The caller configures identity and re-runs; never invent an
|
|
25
|
+
// identity here — the commit belongs to the user, not the crew.
|
|
26
|
+
// - The commit message names exactly what was staged (computed from the
|
|
27
|
+
// index, not from flags).
|
|
28
|
+
//
|
|
29
|
+
// Usage:
|
|
30
|
+
// node lib/commit-scaffold.js --repo <path> [--gitignore]
|
|
31
|
+
// --gitignore: the setup step modified .gitignore (consent was given);
|
|
32
|
+
// include it in the commit.
|
|
33
|
+
// Prints JSON: { committed, sha, reason }
|
|
34
|
+
|
|
35
|
+
const fs = require("fs");
|
|
36
|
+
const path = require("path");
|
|
37
|
+
const { execFileSync } = require("child_process");
|
|
38
|
+
|
|
39
|
+
function git(repoPath, args, env) {
|
|
40
|
+
try {
|
|
41
|
+
// No .trim(): callers that parse fixed-width output (porcelain) need
|
|
42
|
+
// the raw lines; scalar callers trim themselves.
|
|
43
|
+
return execFileSync("git", args, {
|
|
44
|
+
cwd: repoPath,
|
|
45
|
+
encoding: "utf8",
|
|
46
|
+
env: env || process.env,
|
|
47
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
48
|
+
});
|
|
49
|
+
} catch (e) {
|
|
50
|
+
const stderr = e.stderr ? String(e.stderr).trim() : "";
|
|
51
|
+
throw new Error("git " + args.join(" ") + " failed: " + (stderr || e.message));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Porcelain status lines touching exactly relPath (e.g. ".gitignore").
|
|
56
|
+
// Used by the composer as a pre-mutation guard; exported for tests.
|
|
57
|
+
function isPathDirty(repoPath, relPath) {
|
|
58
|
+
const out = git(repoPath, ["status", "--porcelain", "--", relPath]);
|
|
59
|
+
return out.split("\n").some(function (line) {
|
|
60
|
+
if (line.length < 4) return false;
|
|
61
|
+
const p = line.slice(3).replace(/^"|"$/g, "");
|
|
62
|
+
return p === relPath;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function commitScaffold(repoPath, opts) {
|
|
67
|
+
opts = opts || {};
|
|
68
|
+
const includeGitignore = opts.includeGitignore === true;
|
|
69
|
+
|
|
70
|
+
if (!fs.existsSync(path.join(repoPath, ".git"))) {
|
|
71
|
+
return { committed: false, sha: null, reason: "not-a-git-repo" };
|
|
72
|
+
}
|
|
73
|
+
if (!fs.existsSync(path.join(repoPath, ".orchestration"))) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
"no .orchestration/ scaffold found in " + repoPath + " — run the scaffold step before committing"
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const paths = [".orchestration"];
|
|
80
|
+
if (includeGitignore) paths.push(".gitignore");
|
|
81
|
+
git(repoPath, ["add", "--"].concat(paths));
|
|
82
|
+
|
|
83
|
+
const staged = git(repoPath, ["diff", "--cached", "--name-only"]).trim();
|
|
84
|
+
if (!staged) {
|
|
85
|
+
return { committed: false, sha: null, reason: "nothing-to-commit" };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const stagedFiles = staged.split("\n");
|
|
89
|
+
const subject = stagedFiles.indexOf(".gitignore") !== -1
|
|
90
|
+
? "Muse Crew setup: commit .orchestration scaffold and crew .gitignore entries"
|
|
91
|
+
: "Muse Crew setup: commit .orchestration scaffold";
|
|
92
|
+
const body =
|
|
93
|
+
"Setup leaves the repo's main checkout clean: these are the crew-owned\n" +
|
|
94
|
+
"files written during project setup. Integrate refuses to merge onto a\n" +
|
|
95
|
+
"dirty main, so they are committed here instead of left uncommitted.";
|
|
96
|
+
git(repoPath, ["commit", "-m", subject, "-m", body, "--quiet"]);
|
|
97
|
+
const sha = git(repoPath, ["rev-parse", "HEAD"]).trim();
|
|
98
|
+
|
|
99
|
+
return { committed: true, sha: sha, reason: null };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = { commitScaffold, isPathDirty };
|
|
103
|
+
|
|
104
|
+
if (require.main === module) {
|
|
105
|
+
const argv = process.argv.slice(2);
|
|
106
|
+
const get = (flag) => {
|
|
107
|
+
const i = argv.indexOf(flag);
|
|
108
|
+
return i >= 0 ? argv[i + 1] : null;
|
|
109
|
+
};
|
|
110
|
+
const repoPath = get("--repo");
|
|
111
|
+
if (!repoPath) {
|
|
112
|
+
console.error("Usage: node lib/commit-scaffold.js --repo <path> [--gitignore]");
|
|
113
|
+
process.exit(2);
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
console.log(JSON.stringify(commitScaffold(repoPath, { includeGitignore: argv.includes("--gitignore") })));
|
|
117
|
+
} catch (e) {
|
|
118
|
+
console.error("commit-scaffold failed: " + (e.message || e));
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -4,17 +4,37 @@
|
|
|
4
4
|
// Composes the deterministic building blocks after create-project:
|
|
5
5
|
// 1. Scaffold $REPO/.orchestration/ (workflows/identities/phases versioned, user/ ignored)
|
|
6
6
|
// 2. Optionally update .gitignore (only with explicit --consent)
|
|
7
|
+
// 3. Commit the scaffold (and the .gitignore update when consented) so
|
|
8
|
+
// the repo's main checkout is clean when setup finishes.
|
|
9
|
+
//
|
|
10
|
+
// Step 3 is the 2026-09-18 fix for the room #13 defect: 0.11.0 wrote the
|
|
11
|
+
// scaffold but never committed it, and the first task's Integrate then
|
|
12
|
+
// refused to merge onto the dirty main — twice, deterministically. Setup
|
|
13
|
+
// must leave main clean; the commit is mechanical, not agent judgment.
|
|
7
14
|
//
|
|
8
15
|
// This is the CLI path equivalent of what crew-init's dashboard mode does.
|
|
9
16
|
// The dashboard calls the same script. No new API endpoint — composition.
|
|
10
17
|
//
|
|
18
|
+
// Guards:
|
|
19
|
+
// - With --consent, .gitignore must be clean before we touch it. The
|
|
20
|
+
// commit only ever stages .orchestration/ (+ .gitignore when we changed
|
|
21
|
+
// it), but a pre-existing dirty .gitignore would be swept into our
|
|
22
|
+
// commit by `git add` — so refuse loudly instead. Commit or stash it,
|
|
23
|
+
// then re-run.
|
|
24
|
+
// - .orchestration/ is crew-owned: pre-existing uncommitted content there
|
|
25
|
+
// (e.g. left dirty by 0.11.0) is committed, not refused.
|
|
26
|
+
//
|
|
11
27
|
// Usage:
|
|
12
28
|
// node lib/setup-project-repo.js --repo <path> --crew-repo <path> [--consent]
|
|
13
29
|
// --consent: user approved the .gitignore change (from the setup conversation)
|
|
14
|
-
// Prints JSON: { orchestration: {...}, gitignore: {...} | { declined: true } }
|
|
30
|
+
// Prints JSON: { orchestration: {...}, gitignore: {...} | { declined: true }, commit: {...} }
|
|
31
|
+
// Exits non-zero on any failure (fail closed, loud).
|
|
15
32
|
|
|
33
|
+
const fs = require("fs");
|
|
34
|
+
const path = require("path");
|
|
16
35
|
const { scaffoldRepoOrchestration } = require("./repo-orchestration");
|
|
17
36
|
const { ensureGitignoreEntries, describeGitignoreChange } = require("./gitignore");
|
|
37
|
+
const { commitScaffold, isPathDirty } = require("./commit-scaffold");
|
|
18
38
|
|
|
19
39
|
function main() {
|
|
20
40
|
const argv = process.argv.slice(2);
|
|
@@ -31,18 +51,44 @@ function main() {
|
|
|
31
51
|
process.exit(2);
|
|
32
52
|
}
|
|
33
53
|
|
|
34
|
-
|
|
54
|
+
try {
|
|
55
|
+
// Guard: never sweep the user's own .gitignore edits into our commit.
|
|
56
|
+
if (consent && fs.existsSync(path.join(repoPath, ".git")) && isPathDirty(repoPath, ".gitignore")) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
"setup refused: .gitignore has uncommitted changes not made by setup — " +
|
|
59
|
+
"commit or stash them, then re-run setup"
|
|
60
|
+
);
|
|
61
|
+
}
|
|
35
62
|
|
|
36
|
-
|
|
37
|
-
if (consent) {
|
|
38
|
-
gitignore = ensureGitignoreEntries(repoPath);
|
|
39
|
-
} else {
|
|
40
|
-
// Declined (or non-interactive default): report what WOULD change.
|
|
41
|
-
const preview = describeGitignoreChange(repoPath);
|
|
42
|
-
gitignore = { declined: true, wouldChange: preview };
|
|
43
|
-
}
|
|
63
|
+
const orchestration = scaffoldRepoOrchestration(repoPath, crewRepoPath);
|
|
44
64
|
|
|
45
|
-
|
|
65
|
+
let gitignore;
|
|
66
|
+
if (consent) {
|
|
67
|
+
gitignore = ensureGitignoreEntries(repoPath);
|
|
68
|
+
} else {
|
|
69
|
+
// Declined (or non-interactive default): report what WOULD change.
|
|
70
|
+
const preview = describeGitignoreChange(repoPath);
|
|
71
|
+
gitignore = { declined: true, wouldChange: preview };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let commit;
|
|
75
|
+
try {
|
|
76
|
+
commit = commitScaffold(repoPath, { includeGitignore: consent });
|
|
77
|
+
} catch (commitErr) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
"commit failed: " +
|
|
80
|
+
commitErr.message +
|
|
81
|
+
" — the repo has uncommitted crew files; configure git identity and run: " +
|
|
82
|
+
"node lib/commit-scaffold.js --repo <path>" +
|
|
83
|
+
(consent ? " --gitignore" : "")
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log(JSON.stringify({ orchestration, gitignore, commit }, null, 2));
|
|
88
|
+
} catch (e) {
|
|
89
|
+
console.error("setup-project-repo failed: " + (e.message || e));
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
46
92
|
}
|
|
47
93
|
|
|
48
94
|
if (require.main === module) main();
|
package/package.json
CHANGED
package/workflows/crew-init.js
CHANGED
|
@@ -337,47 +337,6 @@ var scaffoldCreated = scaffoldResult.created ? scaffoldResult.created.length : 0
|
|
|
337
337
|
var scaffoldSkipped = scaffoldResult.skipped ? scaffoldResult.skipped.length : 0;
|
|
338
338
|
log("Scaffold: " + scaffoldCreated + " created, " + scaffoldSkipped + " skipped");
|
|
339
339
|
|
|
340
|
-
// ── Gitignore consent execution ─────────────────────────────────────
|
|
341
|
-
// If the user approved during the setup conversation (gitignoreConsent=true)
|
|
342
|
-
// and we have a repo (dashboard mode), append crew-owned paths to .gitignore
|
|
343
|
-
// via the deterministic lib/gitignore.js (no agent judgment — the agent
|
|
344
|
-
// just runs the fixed command; all merge logic is in the tested JS module).
|
|
345
|
-
// This is the ONLY user file outside .orchestration/ that the crew modifies.
|
|
346
|
-
if (gitignoreConsent && dashboardRepoPath) {
|
|
347
|
-
var gitignoreResult;
|
|
348
|
-
try {
|
|
349
|
-
gitignoreResult = await agent(
|
|
350
|
-
"Run the deterministic gitignore updater. Do not decide anything — just run the command and return its JSON output.\n\n" +
|
|
351
|
-
"Command:\n" +
|
|
352
|
-
"node " + crewHome + "/current/lib/gitignore.js --repo \"" + gateFacts.dashboardRepoExpanded + "\"\n\n" +
|
|
353
|
-
"The script prints one JSON line: { created, appended[], skipped[] }.\n" +
|
|
354
|
-
"Return that JSON verbatim.",
|
|
355
|
-
{
|
|
356
|
-
key: "scaffold-gitignore",
|
|
357
|
-
label: "Update .gitignore for crew paths",
|
|
358
|
-
schema: {
|
|
359
|
-
type: "object",
|
|
360
|
-
properties: {
|
|
361
|
-
created: { type: "boolean" },
|
|
362
|
-
appended: { type: "array", items: { type: "string" } },
|
|
363
|
-
skipped: { type: "array", items: { type: "string" } }
|
|
364
|
-
},
|
|
365
|
-
required: ["created", "appended", "skipped"]
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
);
|
|
369
|
-
log("Gitignore: " + (gitignoreResult.created ? "created" : "updated") +
|
|
370
|
-
" — appended [" + (gitignoreResult.appended || []).join(", ") + "]" +
|
|
371
|
-
", already present [" + (gitignoreResult.skipped || []).join(", ") + "]");
|
|
372
|
-
} catch (e) {
|
|
373
|
-
log("Gitignore update failed (non-fatal): " + String(e.message || e));
|
|
374
|
-
}
|
|
375
|
-
} else if (dashboardRepoPath) {
|
|
376
|
-
log("Gitignore: user declined — add '.worktrees/' and '.orchestration/user/' to " + dashboardRepoPath + "/.gitignore manually.");
|
|
377
|
-
} else {
|
|
378
|
-
log("Gitignore: no repo (CLI-only mode) — skipping.");
|
|
379
|
-
}
|
|
380
|
-
|
|
381
340
|
// ── Phase 3: Project registration ─────────────────────────────────────
|
|
382
341
|
// Registers the first project in crew-state.db via the Crew API. In
|
|
383
342
|
// dashboard mode, the dashboard becomes the crew's first project (so the
|
|
@@ -455,37 +414,82 @@ try {
|
|
|
455
414
|
log("Project: " + dashboardSlug + " " + projectResult.action + " (repo_path=" + gateFacts.dashboardRepoExpanded + ")");
|
|
456
415
|
} // end dashboard-mode project registration
|
|
457
416
|
|
|
458
|
-
// ──
|
|
459
|
-
//
|
|
460
|
-
//
|
|
461
|
-
//
|
|
417
|
+
// ── Project repo setup (2026-09-18) ─────────────────────────────────
|
|
418
|
+
// One deterministic composer — lib/setup-project-repo.js — scaffolds
|
|
419
|
+
// $REPO/.orchestration/, applies the consented .gitignore update, and COMMITS
|
|
420
|
+
// both so the repo's main checkout is clean when setup finishes.
|
|
421
|
+
// Room #13 (2026-09-17): the old two-call shape wrote the scaffold but never
|
|
422
|
+
// committed it, and the first task's Integrate then refused to merge onto the
|
|
423
|
+
// dirty main — twice, deterministically. This replaces both calls; the agent
|
|
424
|
+
// still runs one fixed command and all logic stays in the tested lib modules.
|
|
425
|
+
// Best-effort as before (a failed setup never blocks init), but a failed
|
|
426
|
+
// COMMIT is logged loudly: the repo is dirty and the first task will fail
|
|
427
|
+
// Integrate until the scaffold is committed.
|
|
428
|
+
var setupRepoWarning = null;
|
|
429
|
+
var setupRepoResult = null;
|
|
462
430
|
if (dashboardRepoPath) {
|
|
463
|
-
var repoOrchResult;
|
|
464
431
|
try {
|
|
465
|
-
|
|
466
|
-
"
|
|
432
|
+
setupRepoResult = await agent(
|
|
433
|
+
"Run the deterministic project-repo setup composer. Do not decide anything — just run the command and return its JSON output.\n\n" +
|
|
467
434
|
"Command:\n" +
|
|
468
|
-
"node " + crewHome + "/current/lib/repo
|
|
469
|
-
"The script prints JSON
|
|
435
|
+
"node " + crewHome + "/current/lib/setup-project-repo.js --repo \"" + gateFacts.dashboardRepoExpanded + "\" --crew-repo \"" + crewRepoPath + "\"" + (gitignoreConsent ? " --consent" : "") + "\n\n" +
|
|
436
|
+
"The script prints one JSON object: { orchestration, gitignore, commit }.\n" +
|
|
437
|
+
"orchestration carries orchDir, dirsCreated, and per-area created/skipped arrays;\n" +
|
|
438
|
+
"gitignore carries created/appended/skipped (or declined:true with a wouldChange preview);\n" +
|
|
439
|
+
"commit carries { committed, sha, reason }.\n" +
|
|
470
440
|
"Return that JSON verbatim.",
|
|
471
441
|
{
|
|
472
|
-
key: "scaffold-repo-
|
|
473
|
-
label: "
|
|
442
|
+
key: "scaffold-repo-setup",
|
|
443
|
+
label: "Set up project repo (scaffold + gitignore + commit)",
|
|
474
444
|
schema: {
|
|
475
445
|
type: "object",
|
|
476
446
|
properties: {
|
|
477
|
-
|
|
478
|
-
|
|
447
|
+
orchestration: {
|
|
448
|
+
type: "object",
|
|
449
|
+
properties: {
|
|
450
|
+
orchDir: { type: "string" },
|
|
451
|
+
dirsCreated: { type: "array", items: { type: "string" } }
|
|
452
|
+
},
|
|
453
|
+
required: ["orchDir", "dirsCreated"]
|
|
454
|
+
},
|
|
455
|
+
gitignore: { type: "object" },
|
|
456
|
+
commit: {
|
|
457
|
+
type: "object",
|
|
458
|
+
properties: {
|
|
459
|
+
committed: { type: "boolean" },
|
|
460
|
+
sha: { type: ["string", "null"] },
|
|
461
|
+
reason: { type: ["string", "null"] }
|
|
462
|
+
},
|
|
463
|
+
required: ["committed"]
|
|
464
|
+
}
|
|
479
465
|
},
|
|
480
|
-
required: ["
|
|
466
|
+
required: ["orchestration", "gitignore", "commit"]
|
|
481
467
|
}
|
|
482
468
|
}
|
|
483
469
|
);
|
|
484
|
-
|
|
485
|
-
|
|
470
|
+
var orchDirs = (setupRepoResult.orchestration.dirsCreated || []).join(", ");
|
|
471
|
+
var commitMsg = setupRepoResult.commit.committed
|
|
472
|
+
? "committed " + setupRepoResult.commit.sha
|
|
473
|
+
: "not committed (" + setupRepoResult.commit.reason + ")";
|
|
474
|
+
log("Repo setup: scaffolded " + setupRepoResult.orchestration.orchDir +
|
|
475
|
+
" (dirs: [" + orchDirs + "]); gitignore " +
|
|
476
|
+
(gitignoreConsent ? "update consented" : "declined by user — add '.worktrees/' and '.orchestration/user/' manually") +
|
|
477
|
+
"; scaffold " + commitMsg);
|
|
478
|
+
if (!setupRepoResult.commit.committed && setupRepoResult.commit.reason !== "nothing-to-commit") {
|
|
479
|
+
setupRepoWarning = "Project repo setup left the scaffold uncommitted (" +
|
|
480
|
+
setupRepoResult.commit.reason + ") — the first task's Integrate will refuse to merge until it is committed.";
|
|
481
|
+
log("WARNING: " + setupRepoWarning);
|
|
482
|
+
}
|
|
486
483
|
} catch (e) {
|
|
487
|
-
|
|
484
|
+
var setupErr = String(e.message || e);
|
|
485
|
+
setupRepoWarning = "Project repo setup failed: " + setupErr + " — continuing without the repo scaffold.";
|
|
486
|
+
if (/commit failed/i.test(setupErr)) {
|
|
487
|
+
setupRepoWarning += " The repo has uncommitted crew files; the first task's Integrate will refuse to merge until they are committed.";
|
|
488
|
+
}
|
|
489
|
+
log(setupRepoWarning + " (non-fatal)");
|
|
488
490
|
}
|
|
491
|
+
} else {
|
|
492
|
+
log("Repo setup: no repo (CLI-only mode) — skipping.");
|
|
489
493
|
}
|
|
490
494
|
|
|
491
495
|
// ── Scheduler identity (2026-09-16) ──────────────────────────────────
|
|
@@ -746,6 +750,7 @@ return {
|
|
|
746
750
|
releaseHash: releaseResult.hash,
|
|
747
751
|
scaffold: { created: scaffoldCreated, skipped: scaffoldSkipped },
|
|
748
752
|
project: projectResult.action,
|
|
753
|
+
repoSetupWarning: setupRepoWarning,
|
|
749
754
|
crons: cronsResult.summary.crons,
|
|
750
755
|
registry: cronsResult.summary.registry,
|
|
751
756
|
autoUpdate: {
|