@esneiderbravo/speclaw 2.0.14 → 2.0.15
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/dist/cli/commands/init.js +23 -34
- package/dist/cli/lib/help.js +4 -3
- package/dist/shared/agents.js +32 -7
- package/package.json +1 -1
|
@@ -24,17 +24,16 @@ export function detectProjectName(cwd) {
|
|
|
24
24
|
return path.basename(cwd);
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
27
|
+
* Setup: create `.agents/` (plus any `--agents`), pick packs when any exist, scaffold, index,
|
|
28
28
|
* and print the handoff prompt.
|
|
29
29
|
*
|
|
30
|
-
* @param flags - Parsed flags;
|
|
31
|
-
* `-y` is set. Honors `--project-name`, `--packs`, and `--no-index`.
|
|
30
|
+
* @param flags - Parsed flags; only the pack picker is interactive (on a TTY unless
|
|
31
|
+
* `--agents`, `--yes`, or `-y` is set). Honors `--project-name`, `--packs`, and `--no-index`.
|
|
32
32
|
*/
|
|
33
33
|
export async function runInit(flags) {
|
|
34
34
|
const cwd = process.cwd();
|
|
35
35
|
const projectName = flags["project-name"] || detectProjectName(cwd);
|
|
36
36
|
const interactive = Boolean(process.stdin.isTTY) && !flags.agents && !flags.yes && !flags.y;
|
|
37
|
-
let agents;
|
|
38
37
|
let packs;
|
|
39
38
|
const availablePacks = loadPacks();
|
|
40
39
|
const packIds = Object.keys(availablePacks);
|
|
@@ -50,36 +49,26 @@ export async function runInit(flags) {
|
|
|
50
49
|
`Later, ${ui.code("speclaw update")} upgrades itself through npx.`);
|
|
51
50
|
ui.plain();
|
|
52
51
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
const answers = await clack.group(prompts, { onCancel: () => process.exit(1) });
|
|
76
|
-
agents = answers.agents;
|
|
77
|
-
packs = answers.packs ?? [];
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
// Default: one agent-facing folder, `.agents/`, linked into ai-specs/.
|
|
81
|
-
agents = list(flags.agents).length ? list(flags.agents) : ["agents"];
|
|
82
|
-
packs = list(flags.packs);
|
|
52
|
+
// Agents are never prompted for: init creates one agent-facing folder,
|
|
53
|
+
// `.agents/`, linked into ai-specs/. Other IDE folders only via `--agents`
|
|
54
|
+
// or `speclaw agent add`.
|
|
55
|
+
const agents = list(flags.agents).length ? list(flags.agents) : ["agents"];
|
|
56
|
+
packs = list(flags.packs);
|
|
57
|
+
// Covers: req~remove-agents-pack~1
|
|
58
|
+
if (interactive && packIds.length > 0 && !packs.length) {
|
|
59
|
+
const answer = await clack.multiselect({
|
|
60
|
+
message: "Which tool packs to install?",
|
|
61
|
+
options: packIds.map((id) => ({
|
|
62
|
+
value: id,
|
|
63
|
+
label: id,
|
|
64
|
+
hint: availablePacks[id].description.slice(0, 50),
|
|
65
|
+
})),
|
|
66
|
+
initialValues: [],
|
|
67
|
+
required: false,
|
|
68
|
+
});
|
|
69
|
+
if (clack.isCancel(answer))
|
|
70
|
+
process.exit(1);
|
|
71
|
+
packs = answer;
|
|
83
72
|
}
|
|
84
73
|
const unknownAgents = agents.filter((a) => !agentById(a));
|
|
85
74
|
if (unknownAgents.length) {
|
package/dist/cli/lib/help.js
CHANGED
|
@@ -13,7 +13,7 @@ Install globally so the command is always available:
|
|
|
13
13
|
npm i -g @esneiderbravo/speclaw (or run npx @esneiderbravo/speclaw@latest init)
|
|
14
14
|
|
|
15
15
|
Setup
|
|
16
|
-
init
|
|
16
|
+
init Setup: .agents/ linked into ai-specs/, scaffold, index, get the prompt
|
|
17
17
|
(--minimal omits setup/lifecycle MCP tools)
|
|
18
18
|
update Re-run itself at the latest version via npx, then apply project
|
|
19
19
|
migrations (--check reports version only; --no-self-update
|
|
@@ -123,8 +123,9 @@ you rarely run it by hand.
|
|
|
123
123
|
name: "init",
|
|
124
124
|
usage: `Usage: speclaw init [options]
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
Compass index, and print the
|
|
126
|
+
Setup without an agent picker: create .agents/ linked into ai-specs/ (gitignored),
|
|
127
|
+
scaffold the constitution and lawbook, build the Compass index, and print the
|
|
128
|
+
starting prompt. Prefer
|
|
128
129
|
npx @esneiderbravo/speclaw@latest init so the scaffold matches the latest release.
|
|
129
130
|
|
|
130
131
|
Options
|
package/dist/shared/agents.js
CHANGED
|
@@ -11,6 +11,7 @@ export const AGENTS = [
|
|
|
11
11
|
linkTargets: ["skills", "commands", "agents"],
|
|
12
12
|
mcpFile: ".mcp.json",
|
|
13
13
|
hooks: { file: ".claude/settings.json", key: "hooks" },
|
|
14
|
+
ownedPaths: [".claude/rules/speclaw"],
|
|
14
15
|
},
|
|
15
16
|
{
|
|
16
17
|
id: "cursor",
|
|
@@ -141,11 +142,23 @@ export function configureAgent(projectPath, agentId, report) {
|
|
|
141
142
|
if (!agent)
|
|
142
143
|
throw new Error(`Unknown agent "${agentId}". Known: ${AGENTS.map((a) => a.id).join(", ")}`);
|
|
143
144
|
const ideRoot = path.join(projectPath, agent.ideDir);
|
|
144
|
-
//
|
|
145
|
-
// ai-specs/
|
|
146
|
-
// folder
|
|
145
|
+
// Everything speclaw puts in an agent folder is links into the gitignored
|
|
146
|
+
// ai-specs/ plus per-developer wiring: keep it out of version control. A
|
|
147
|
+
// folder speclaw creates is ignored whole; in a folder that already existed
|
|
148
|
+
// (the IDE's own, e.g. `.claude/settings.local.json`) only speclaw's entries are.
|
|
149
|
+
const comment = `speclaw ${agent.label} links (regenerated by init; never commit)`;
|
|
147
150
|
if (!fs.existsSync(ideRoot)) {
|
|
148
|
-
ensureGitignore(projectPath, `${agent.ideDir}/`,
|
|
151
|
+
ensureGitignore(projectPath, `${agent.ideDir}/`, comment, report);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
const owned = agent.linkTargets
|
|
155
|
+
.filter((t) => fs.existsSync(path.join(projectPath, "ai-specs", t)))
|
|
156
|
+
.map((t) => `${agent.ideDir}/${t}`)
|
|
157
|
+
.concat(agent.ownedPaths ?? []);
|
|
158
|
+
if (agent.hooks && !fs.existsSync(path.join(projectPath, agent.hooks.file)))
|
|
159
|
+
owned.push(agent.hooks.file);
|
|
160
|
+
for (const entry of owned)
|
|
161
|
+
ensureGitignore(projectPath, entry, comment, report);
|
|
149
162
|
}
|
|
150
163
|
for (const target of agent.linkTargets) {
|
|
151
164
|
const sourceDir = path.join(projectPath, "ai-specs", target);
|
|
@@ -164,13 +177,25 @@ export function configureAgent(projectPath, agentId, report) {
|
|
|
164
177
|
writeMcpConfig(projectPath, agent.mcpFile, report);
|
|
165
178
|
}
|
|
166
179
|
/**
|
|
167
|
-
* List the agents
|
|
180
|
+
* List the agents speclaw set up in this project: those whose IDE dir holds at
|
|
181
|
+
* least one speclaw link into ai-specs/. A bare IDE dir is not enough — Claude
|
|
182
|
+
* Code and Cursor create `.claude/` / `.cursor/` on their own, and treating
|
|
183
|
+
* that as "configured" made `update` populate folders nobody chose.
|
|
168
184
|
*
|
|
169
185
|
* @param projectPath - Project root to inspect.
|
|
170
|
-
* @returns The ids of agents
|
|
186
|
+
* @returns The ids of agents with a speclaw link in their IDE directory.
|
|
171
187
|
*/
|
|
172
188
|
export function detectConfiguredAgents(projectPath) {
|
|
173
|
-
return AGENTS.filter((a) =>
|
|
189
|
+
return AGENTS.filter((a) => a.linkTargets.some((t) => isAiSpecsLink(path.join(projectPath, a.ideDir, t), t))).map((a) => a.id);
|
|
190
|
+
}
|
|
191
|
+
/** True when `p` is a symlink whose target ends in `ai-specs/<target>`. */
|
|
192
|
+
function isAiSpecsLink(p, target) {
|
|
193
|
+
try {
|
|
194
|
+
return fs.readlinkSync(p).replace(/\\/g, "/").endsWith(`ai-specs/${target}`);
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
174
199
|
}
|
|
175
200
|
/**
|
|
176
201
|
* Re-run configuration for every already-configured agent (e.g. after adding a pack).
|