@warnyin/sdlc 0.14.0 → 0.14.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 +16 -0
- package/bin/cli.mjs +46 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.1 (2026-09-19)
|
|
4
|
+
|
|
5
|
+
- **Fix (init)**: adding a tool to a project that already has `sdlc/` no longer loses it on the
|
|
6
|
+
next update. `init --tool <newtool>` installed the tool's files but never recorded it in
|
|
7
|
+
`sdlc/config.yaml`'s `tools:` line, so the next plain `update` read that stale list and
|
|
8
|
+
**silently pruned the files it had just installed**. `init` now records what it installs,
|
|
9
|
+
added to whatever was already listed — never removing an entry, because `init` has no prune
|
|
10
|
+
capability and this does not give it one. Only `update --tool <list>` still replaces the list
|
|
11
|
+
wholesale and prunes what is left out. The install summary says `(recorded <tools>)` when the
|
|
12
|
+
list actually grew, and only then. A `config.yaml` carrying no `tools:` line at all is still
|
|
13
|
+
left alone, exactly as `update` already leaves it.
|
|
14
|
+
- **Fix (config)**: the `tools:` line rewrite — now shared by `init` and `update` instead of
|
|
15
|
+
living inline in one of them — passes its replacement as a function rather than a string, so
|
|
16
|
+
a tool name containing `$&`, `` $` `` or `$'` in a hand-edited config is written literally
|
|
17
|
+
instead of being interpreted by `String.replace` and mangling the surrounding file.
|
|
18
|
+
|
|
3
19
|
## 0.14.0 (2026-09-19)
|
|
4
20
|
|
|
5
21
|
- **Feature (init)**: `kimi` (Kimi Code CLI) is now a supported tool. Selecting or detecting it
|
package/bin/cli.mjs
CHANGED
|
@@ -312,7 +312,7 @@ export async function resolveTools(args, {
|
|
|
312
312
|
return picked;
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
-
function printInitSummary(tools, ctx, style, symbols, { configExisted }) {
|
|
315
|
+
function printInitSummary(tools, ctx, style, symbols, { configExisted, toolsAdded }) {
|
|
316
316
|
const s = summarizeInstall(ctx.manifest.keys(), tools);
|
|
317
317
|
const stats = ctx.stats;
|
|
318
318
|
const line = (text) => console.log(` ${text}`);
|
|
@@ -329,7 +329,10 @@ function printInitSummary(tools, ctx, style, symbols, { configExisted }) {
|
|
|
329
329
|
}
|
|
330
330
|
line(`${s.hooks} hooks in sdlc/.hooks/`);
|
|
331
331
|
line(`Playbook: sdlc/.playbook/ (${s.playbook} stages + ${s.templates} templates)`);
|
|
332
|
-
|
|
332
|
+
const configNote = toolsAdded?.length
|
|
333
|
+
? ` (recorded ${toolsAdded.map(toolName).join(', ')})`
|
|
334
|
+
: configExisted ? ' (kept)' : '';
|
|
335
|
+
line(`Config: sdlc/config.yaml${configNote}`);
|
|
333
336
|
line(style.dim(`Files: ${stats.written} written · ${stats.current} unchanged · ${stats.updated} refreshed · ${stats.kept} kept (yours)`));
|
|
334
337
|
console.log('');
|
|
335
338
|
console.log(` ${style.bold('Getting started:')}`);
|
|
@@ -341,7 +344,26 @@ export async function cmdInit(projectRoot, args) {
|
|
|
341
344
|
const style = createStyle(colorEnabled());
|
|
342
345
|
const symbols = symbolsFor();
|
|
343
346
|
const tools = await resolveTools(args, { projectRoot, style });
|
|
344
|
-
const
|
|
347
|
+
const configPath = path.join(projectRoot, 'sdlc', 'config.yaml');
|
|
348
|
+
const configExisted = fs.existsSync(configPath);
|
|
349
|
+
|
|
350
|
+
// config.yaml is a seed — written once, never rewritten wholesale. But its `tools:` line
|
|
351
|
+
// is the one field this command promises to keep current ("filled by `warnyin-sdlc init`"
|
|
352
|
+
// in the template), so a second init adding a tool must not leave it stale: union what's
|
|
353
|
+
// already recorded with what this run installs, never removing an entry (init has no prune
|
|
354
|
+
// capability, and this doesn't give it one — only `update --tool <list>` can shrink the set).
|
|
355
|
+
let toolsAdded = [];
|
|
356
|
+
if (configExisted) {
|
|
357
|
+
const recorded = parseConfig(fs.readFileSync(configPath, 'utf8')).tools;
|
|
358
|
+
const newlySelected = tools.filter((t) => !recorded.includes(t));
|
|
359
|
+
// Only claim "recorded" in the summary when a write actually happened — a config
|
|
360
|
+
// predating the `tools:` key makes persistToolsLine a no-op, and the message must not
|
|
361
|
+
// say otherwise.
|
|
362
|
+
if (newlySelected.length && persistToolsLine(configPath, [...recorded, ...newlySelected])) {
|
|
363
|
+
toolsAdded = newlySelected;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
345
367
|
const ctx = {
|
|
346
368
|
mode: 'install',
|
|
347
369
|
manifest: new Map(),
|
|
@@ -354,7 +376,7 @@ export async function cmdInit(projectRoot, args) {
|
|
|
354
376
|
writeManifestFile(projectRoot, ctx.manifest);
|
|
355
377
|
ensureGitignore(projectRoot);
|
|
356
378
|
for (const w of ctx.warnings) console.warn(` ${style.yellow(symbols.warn)} ${w}`);
|
|
357
|
-
printInitSummary(tools, ctx, style, symbols, { configExisted });
|
|
379
|
+
printInitSummary(tools, ctx, style, symbols, { configExisted, toolsAdded });
|
|
358
380
|
return { tools };
|
|
359
381
|
}
|
|
360
382
|
|
|
@@ -395,6 +417,25 @@ export function forceNeedsAPerson(args, env = process.env, stdin = process.stdin
|
|
|
395
417
|
+ ' Run it yourself, or set WARNYIN_SDLC_FORCE=1 if this really is automation that meant it.';
|
|
396
418
|
}
|
|
397
419
|
|
|
420
|
+
// Rewrites just the `tools:` line of `config.yaml` in place, leaving every other line
|
|
421
|
+
// untouched — a no-op when the file carries no `tools:` line at all (a config predating the
|
|
422
|
+
// key; matched, not fixed). Shared by `cmdInit` and `cmdUpdate` so the two paths can't drift.
|
|
423
|
+
// Reproduces the existing behavior exactly, trailing inline comment included: the line is
|
|
424
|
+
// replaced wholesale, so a hand-added comment after `tools: [...]` does not survive a rewrite.
|
|
425
|
+
// Returns whether it actually wrote — callers that report "recorded X" to the user must not
|
|
426
|
+
// claim it when this was a no-op (no `tools:` line to rewrite).
|
|
427
|
+
export function persistToolsLine(configPath, tools) {
|
|
428
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
429
|
+
if (!/^tools:/m.test(raw)) return false;
|
|
430
|
+
// A replacer FUNCTION, not a string: `recorded` (unioned in by cmdInit) is free text read
|
|
431
|
+
// back from the project's own config.yaml, not validated against the TOOLS registry the way
|
|
432
|
+
// CLI-sourced tool names are — a string replacement would let `$&`/`$'`/`` $` `` in a
|
|
433
|
+
// hand-edited or corrupted config get interpreted as replacement patterns instead of literal
|
|
434
|
+
// text, silently duplicating or mangling the surrounding file.
|
|
435
|
+
fs.writeFileSync(configPath, raw.replace(/^tools:.*$/m, () => `tools: [${tools.join(', ')}]`));
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
|
|
398
439
|
export function cmdUpdate(projectRoot, args) {
|
|
399
440
|
const refusal = refuseSelfUpdate(projectRoot, PKG_ROOT);
|
|
400
441
|
if (refusal) throw new Error(refusal);
|
|
@@ -414,9 +455,7 @@ export function cmdUpdate(projectRoot, args) {
|
|
|
414
455
|
// Persist an explicit --tool override so declared and installed state never
|
|
415
456
|
// diverge (otherwise pruning tool-specific files leaves config.yaml stale).
|
|
416
457
|
if (args.toolProvided) {
|
|
417
|
-
|
|
418
|
-
const raw = fs.readFileSync(configPath, 'utf8');
|
|
419
|
-
fs.writeFileSync(configPath, raw.replace(/^tools:.*$/m, `tools: [${tools.join(', ')}]`));
|
|
458
|
+
persistToolsLine(path.join(sdlcRoot, 'config.yaml'), tools);
|
|
420
459
|
}
|
|
421
460
|
|
|
422
461
|
// Read before scaffolding: recordPayloadVersion overwrites version.json with our own.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warnyin/sdlc",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "Spec-driven, AI-driven SDLC framework — token-lean specs, contract-first changes, autonomous pipeline with managed hooks. Operationalizes the Day-1 'New SDLC with Vibe Coding' work process.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|