sillyspec 3.26.7 → 3.26.9
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/.claude/skills/sillyspec-archive/SKILL.md +1 -2
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +1 -2
- package/.claude/skills/sillyspec-doctor/SKILL.md +2 -0
- package/.claude/skills/sillyspec-execute/SKILL.md +10 -2
- package/.claude/skills/sillyspec-explore/SKILL.md +0 -1
- package/.claude/skills/sillyspec-plan/SKILL.md +1 -2
- package/.claude/skills/sillyspec-quick/SKILL.md +1 -2
- package/.claude/skills/sillyspec-scan/SKILL.md +2 -2
- package/.claude/skills/sillyspec-status/SKILL.md +0 -1
- package/.claude/skills/sillyspec-verify/SKILL.md +1 -2
- package/.husky/pre-push +9 -2
- package/README.md +5 -9
- package/bin/sillyspec.js +1 -1
- package/package.json +2 -2
- package/packages/dashboard/server/index.js +14 -8
- package/src/change-risk-profile.js +5 -207
- package/src/config-schema.js +310 -287
- package/src/constants.js +97 -106
- package/src/db-engine.js +1 -1
- package/src/docs-check.js +362 -0
- package/src/docs-debt.js +226 -0
- package/src/docs-gate.js +106 -0
- package/src/doctor-diagnostics.js +23 -2
- package/src/git-helper.js +2 -2
- package/src/hooks/worktree-guard.js +1 -1
- package/src/index.js +1963 -1787
- package/src/init.js +95 -17
- package/src/machine-interface.js +7 -4
- package/src/modules.js +490 -486
- package/src/progress/stage-machine.js +3 -0
- package/src/progress.js +37 -13
- package/src/quicklog.js +182 -1
- package/src/run/command.js +154 -26
- package/src/run/complete-handlers.js +1254 -1128
- package/src/run/complete.js +9 -33
- package/src/run/gates.js +85 -1
- package/src/run/prompt.js +82 -1
- package/src/run/quick-audit.js +23 -0
- package/src/run/shared.js +273 -5
- package/src/run/stage.js +17 -1
- package/src/scan-diff.js +388 -0
- package/src/scan-staleness.js +95 -0
- package/src/stage-contract-spec.js +16 -1
- package/src/stage-contract.js +8 -5
- package/src/stage-review.js +1 -1
- package/src/stages/brainstorm-auto.js +0 -1
- package/src/stages/brainstorm.js +3 -3
- package/src/stages/execute.js +35 -11
- package/src/stages/plan-postcheck.js +93 -36
- package/src/stages/plan.js +22 -7
- package/src/stages/quick.js +1 -1
- package/src/stages/scan.js +2 -2
- package/src/stages/verify.js +11 -12
- package/src/sync.js +39 -17
- package/src/task-review.js +14 -5
- package/src/workflow.js +8 -185
- package/src/worktree-apply.js +128 -3
- package/src/worktree.js +2 -2
- package/templates/claude-instruction.md +1 -1
- package/templates/prompts/taskcard-rules.md +1 -0
- package/templates/prompts/verify-probes.md +2 -2
- package/templates/skills/sillyspec-onboard/SKILL.md +0 -92
package/src/init.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'fs';
|
|
2
2
|
import { join, resolve, dirname, basename } from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
4
4
|
import { checkbox, confirm, input } from '@inquirer/prompts';
|
|
5
5
|
import { ProgressManager } from './progress.js';
|
|
6
6
|
import chalk from 'chalk';
|
|
@@ -213,17 +213,27 @@ function isTTY() {
|
|
|
213
213
|
|
|
214
214
|
// ── 核心安装逻辑 ──
|
|
215
215
|
|
|
216
|
-
function doInstall(projectDir, tools, subprojects = [], specDir = null) {
|
|
216
|
+
function doInstall(projectDir, tools, subprojects = [], specDir = null, options = {}) {
|
|
217
217
|
// specDir: 规范目录(默认 projectDir/.sillyspec)
|
|
218
218
|
// projectDir: 源码项目根目录(用于工具检测、指令注入、.gitignore)
|
|
219
|
+
// options.noSkills: 跳过 skills 复制段(--no-skills;指令注入不受影响)
|
|
220
|
+
// options.platformMode: 平台模式(cmdInit 收到 platformOpts 非空)——项目内 .sillyspec/
|
|
221
|
+
// 通常只有 local.yaml(平台 init lease 写、含用户手调 mcp 段),任何清理都会丢失,整体跳过
|
|
222
|
+
const { noSkills = false, platformMode = false } = options;
|
|
219
223
|
const spec = specDir || join(projectDir, '.sillyspec');
|
|
220
224
|
|
|
221
225
|
// 外部 specDir 时清理旧版本残留的 cwd/.sillyspec/(防止源码污染)。
|
|
222
226
|
// ⚠️ 必须保护真实资产:若本地 .sillyspec 含 changes/(非空)、projects/(非空)
|
|
223
227
|
// 或 sillyspec.db(进度库),说明该项目本身就用 SillySpec 管理,整体删除会丢资产。
|
|
224
228
|
// 此时只清运行时残留,拒绝整删;确无资产时才视为旧残留清理。
|
|
229
|
+
// 平台模式(platformMode)整体绕过清理段:项目内 .sillyspec/ 常只有 local.yaml
|
|
230
|
+
// (平台 init lease 第 5 步写,含用户手调 mcp 段),整删/残留清理都会丢配置。
|
|
225
231
|
const legacyDir = join(projectDir, '.sillyspec');
|
|
226
|
-
if (
|
|
232
|
+
if (platformMode) {
|
|
233
|
+
if (specDir && existsSync(legacyDir)) {
|
|
234
|
+
console.log('⏭️ 平台模式:跳过项目内 .sillyspec/ 清理(保留 local.yaml 等本地配置)');
|
|
235
|
+
}
|
|
236
|
+
} else if (specDir && existsSync(legacyDir)) {
|
|
227
237
|
let hasChanges = false;
|
|
228
238
|
try {
|
|
229
239
|
const changesDir = join(legacyDir, 'changes');
|
|
@@ -330,7 +340,12 @@ function doInstall(projectDir, tools, subprojects = [], specDir = null) {
|
|
|
330
340
|
const isExternalSpec = specDir && resolve(spec) !== resolve(projectDir, '.sillyspec');
|
|
331
341
|
if (!isExternalSpec) {
|
|
332
342
|
const gitignorePath = join(projectDir, '.gitignore');
|
|
333
|
-
|
|
343
|
+
// 平台模式项目根落盘物(指针/声明/cleaned 标记)为本地运行时状态,不入库。
|
|
344
|
+
// 注意平台模式(外部 specDir)不进此分支——那两个文件照样不提交,由用户项目自己 ignore。
|
|
345
|
+
const ignoreRules = [
|
|
346
|
+
'.sillyspec/codebase/SCAN-RAW.md', '.sillyspec/local.yaml', '.sillyspec/.runtime/',
|
|
347
|
+
'.sillyspec-platform.json', '.sillyspec-platform-managed', '.sillyspec-platform-cleaned',
|
|
348
|
+
];
|
|
334
349
|
if (existsSync(gitignorePath)) {
|
|
335
350
|
const content = readFileSync(gitignorePath, 'utf8');
|
|
336
351
|
let updated = content.trimEnd();
|
|
@@ -358,7 +373,11 @@ function doInstall(projectDir, tools, subprojects = [], specDir = null) {
|
|
|
358
373
|
injectClaudeInstructions(projectDir);
|
|
359
374
|
}
|
|
360
375
|
|
|
361
|
-
// 复制 skills
|
|
376
|
+
// 复制 skills 到各工具目录(--no-skills 可跳过:platform init 等场景勿污染项目内工具目录)
|
|
377
|
+
if (noSkills) {
|
|
378
|
+
console.log(chalk.dim(' ⏭️ 已跳过 skills 复制(--no-skills)'));
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
362
381
|
const skillToolDirs = {
|
|
363
382
|
claude: '.claude/skills',
|
|
364
383
|
codex: '.codex/skills',
|
|
@@ -386,6 +405,44 @@ function doInstall(projectDir, tools, subprojects = [], specDir = null) {
|
|
|
386
405
|
|
|
387
406
|
// ── 安装完成总结 ──
|
|
388
407
|
|
|
408
|
+
/**
|
|
409
|
+
* 平台模式 init 落盘指针(消除 init→scan 窗口期断点)。
|
|
410
|
+
*
|
|
411
|
+
* 触发条件:带平台专属信号(--workspace-id / --runtime-root,由 index.js 解析为 platformOpts)
|
|
412
|
+
* 且 --spec-dir 指向外部规范目录。仅 --spec-dir(本地外部目录用法)不触发——与 runCommand 侧
|
|
413
|
+
* 「带平台 flag 才算平台调用」判定一致,防本地用户误触发平台指针。
|
|
414
|
+
*
|
|
415
|
+
* 复用 scan 的指针生成逻辑(writePlatformPointer,单一数据源),status: active
|
|
416
|
+
* (POINTER_STATUS.ACTIVE,未 scan;24h STALE 清理只作用于 scan_completed,不会误删)。
|
|
417
|
+
*/
|
|
418
|
+
function writeInitPlatformPointer(projectDir, resolvedSpecDir, platformOpts) {
|
|
419
|
+
if (!platformOpts || !resolvedSpecDir) return;
|
|
420
|
+
const isExternalSpec = resolve(resolvedSpecDir) !== resolve(projectDir, '.sillyspec');
|
|
421
|
+
if (!isExternalSpec) return;
|
|
422
|
+
const { writePlatformPointer } = require_or_import_shared();
|
|
423
|
+
const ok = writePlatformPointer(projectDir, {
|
|
424
|
+
specRoot: resolve(resolvedSpecDir),
|
|
425
|
+
runtimeRoot: platformOpts.runtimeRoot || null,
|
|
426
|
+
workspaceId: platformOpts.workspaceId || null,
|
|
427
|
+
scanRunId: null,
|
|
428
|
+
}, { status: 'active' });
|
|
429
|
+
if (ok) {
|
|
430
|
+
console.log(chalk.green(` ✓ 平台指针已落盘(.sillyspec-platform.json,status: active)`));
|
|
431
|
+
console.log(chalk.green(` init→scan 窗口期裸调 run 命令将自动恢复平台 specRoot: ${resolvedSpecDir}`));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// ESM 下惰性加载 run/shared.js 的 writePlatformPointer。
|
|
436
|
+
// 用 createRequire 而非顶层静态 import:init.js 被 index.js 按 case 动态加载已隔离,
|
|
437
|
+
// 但 shared.js → git-helper 链在 CJS require 下无需解析 ESM 依赖图,路径最短。
|
|
438
|
+
// (node:module 为内置模块,顶层 import 零依赖税。)
|
|
439
|
+
import { createRequire } from 'node:module';
|
|
440
|
+
function require_or_import_shared() {
|
|
441
|
+
const req = createRequire(import.meta.url);
|
|
442
|
+
return req('./run/shared.js');
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
|
|
389
446
|
function showSummary(version, tools, specDir) {
|
|
390
447
|
const toolLabels = tools.map(t => TOOL_LABELS[t] || t);
|
|
391
448
|
|
|
@@ -409,10 +466,18 @@ function showSummary(version, tools, specDir) {
|
|
|
409
466
|
// ── 主命令 ──
|
|
410
467
|
|
|
411
468
|
export async function cmdInit(projectDir, options = {}) {
|
|
412
|
-
const { tool, interactive, specDir } = options;
|
|
469
|
+
const { tool, tools: toolsOpt, interactive, specDir, noSkills = false, platformOpts = null } = options;
|
|
413
470
|
const version = getVersion();
|
|
414
471
|
const resolvedSpecDir = specDir ? resolve(specDir) : null;
|
|
415
472
|
|
|
473
|
+
// C14c:绿地/棕地引导——必须在 doInstall 之前捕获目录状态(init 会创建 .sillyspec/CLAUDE.md/
|
|
474
|
+
// .gitignore 等,事后检测会误判);棕地(已有非隐藏源码)建议 scan,绿地(空目录)建议 brainstorm。
|
|
475
|
+
let brownfield = false;
|
|
476
|
+
try {
|
|
477
|
+
const entries = readdirSync(projectDir, { withFileTypes: true });
|
|
478
|
+
brownfield = entries.some(e => !e.name.startsWith('.'));
|
|
479
|
+
} catch { brownfield = false; }
|
|
480
|
+
|
|
416
481
|
// ── 交互式模式(--interactive 或 -i)──
|
|
417
482
|
if (interactive && isTTY()) {
|
|
418
483
|
// 欢迎画面
|
|
@@ -509,7 +574,8 @@ export async function cmdInit(projectDir, options = {}) {
|
|
|
509
574
|
}
|
|
510
575
|
|
|
511
576
|
console.log('');
|
|
512
|
-
await doInstall(projectDir, selectedTools, subprojects, resolvedSpecDir);
|
|
577
|
+
await doInstall(projectDir, selectedTools, subprojects, resolvedSpecDir, { noSkills, platformMode: platformOpts != null });
|
|
578
|
+
writeInitPlatformPointer(projectDir, resolvedSpecDir, platformOpts);
|
|
513
579
|
showSummary(version, selectedTools, resolvedSpecDir);
|
|
514
580
|
return;
|
|
515
581
|
}
|
|
@@ -517,18 +583,27 @@ export async function cmdInit(projectDir, options = {}) {
|
|
|
517
583
|
// ── 默认快速模式:检测 → 安装 → 结束 ──
|
|
518
584
|
|
|
519
585
|
let tools = [];
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
586
|
+
// --tool 多值(tools 数组,index.js 解析逗号分隔/重复 flag 收集)优先;
|
|
587
|
+
// 兼容旧 tool 单值(程序化调用)。两者都校验 VALID_TOOLS 并合并去重(保序)。
|
|
588
|
+
const requested = [
|
|
589
|
+
...(Array.isArray(toolsOpt) && toolsOpt.length > 0 ? toolsOpt : []),
|
|
590
|
+
...(tool ? [tool] : []),
|
|
591
|
+
];
|
|
592
|
+
if (requested.length > 0) {
|
|
593
|
+
for (const t of requested) {
|
|
594
|
+
if (!VALID_TOOLS.includes(t)) {
|
|
595
|
+
console.error(`❌ 未知工具: ${t}`);
|
|
596
|
+
console.error(`支持的工具: ${VALID_TOOLS.join(', ')}`);
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}
|
|
525
599
|
}
|
|
526
|
-
tools = [
|
|
600
|
+
tools = [...new Set(requested)];
|
|
527
601
|
} else {
|
|
528
602
|
tools = detectTools(projectDir);
|
|
529
603
|
}
|
|
530
604
|
|
|
531
|
-
await doInstall(projectDir, tools, [], resolvedSpecDir);
|
|
605
|
+
await doInstall(projectDir, tools, [], resolvedSpecDir, { noSkills, platformMode: platformOpts != null });
|
|
606
|
+
writeInitPlatformPointer(projectDir, resolvedSpecDir, platformOpts);
|
|
532
607
|
|
|
533
608
|
console.log('');
|
|
534
609
|
console.log(chalk.green(` ✅ SillySpec v${version} 安装完成!`));
|
|
@@ -536,9 +611,12 @@ export async function cmdInit(projectDir, options = {}) {
|
|
|
536
611
|
const specDisplay = resolvedSpecDir || '.sillyspec';
|
|
537
612
|
console.log(` 📁 规范目录: ${chalk.cyan(specDisplay)}`);
|
|
538
613
|
console.log('');
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
614
|
+
// C14c:绿地/棕地引导区分(README:73 棕地应 scan)——early 捕获的目录状态(init 前),
|
|
615
|
+
// 棕地有代码零上下文进 brainstorm 会迷失,建议先 scan 生成架构文档。
|
|
616
|
+
const nextCmd = brownfield ? '/sillyspec:scan' : '/sillyspec:brainstorm';
|
|
617
|
+
console.log(' 下一步:使用 AI 技能开始工作' + (brownfield ? '(检测到现有代码,建议先扫描生成架构文档)' : ''));
|
|
618
|
+
console.log(` OpenClaw: ${chalk.bold(nextCmd)}`);
|
|
619
|
+
console.log(` Claude Code: ${chalk.bold(nextCmd)}`);
|
|
542
620
|
console.log('');
|
|
543
621
|
console.log(chalk.dim(' 💡 增强能力:sillyspec setup(安装 MCP 工具)'));
|
|
544
622
|
console.log('');
|
package/src/machine-interface.js
CHANGED
|
@@ -113,14 +113,16 @@ export function buildEnvelope({
|
|
|
113
113
|
*/
|
|
114
114
|
export async function runGate(stage, changeName, { cwd, specBase, runtimeRoot, specDriftAnchor, ctx = null } = {}) {
|
|
115
115
|
const specRoot = specBase || resolveSpecDir(cwd);
|
|
116
|
-
|
|
116
|
+
// A4:pm 用 specRoot(而非默认 resolveSpecDir(cwd))——--spec-dir/平台模式下 specBase 是真实
|
|
117
|
+
// specDir,默认解析会指向本地孤儿库,与下方 planContent/changeDir 的 specRoot 两套事实源混拼。
|
|
118
|
+
const pm = new ProgressManager({ specDir: specRoot });
|
|
117
119
|
|
|
118
120
|
try {
|
|
119
121
|
// ── 只读契约守卫(D-002@v1):db 不存在时 fail-closed 返回 exit 2,不触发建库落盘 ──
|
|
120
122
|
// pm.read → _ensureDB → db.init() 在 db 不存在/schema 戳落后时会 _createSchema + _save 落盘
|
|
121
123
|
// (db.js:init),这会让一个声明只读的查询命令在 spec 目录凭空建 sillyspec.db,违反只读契约。
|
|
122
124
|
// db 还没建 = 该变更从未跑过流程,无从核验 → 显式返回「无法核验」而非建库。
|
|
123
|
-
const dbPath = join(
|
|
125
|
+
const dbPath = join(specRoot, '.runtime', 'sillyspec.db');
|
|
124
126
|
if (!existsSync(dbPath)) {
|
|
125
127
|
const envelope = buildEnvelope({
|
|
126
128
|
command: 'gate',
|
|
@@ -356,11 +358,12 @@ export async function runDerive(facet, changeName, { cwd, specBase, runtimeRoot,
|
|
|
356
358
|
}
|
|
357
359
|
|
|
358
360
|
const specRoot = specBase || resolveSpecDir(cwd);
|
|
359
|
-
|
|
361
|
+
// A4:同 runGate——pm 用 specRoot 统一事实源,平台/--spec-dir 模式不读本地孤儿库。
|
|
362
|
+
const pm = new ProgressManager({ specDir: specRoot });
|
|
360
363
|
|
|
361
364
|
try {
|
|
362
365
|
// ── 只读契约守卫(D-002@v1,同 runGate):db 不存在时 fail-closed 返回 exit 2,不建库落盘 ──
|
|
363
|
-
const dbPath = join(
|
|
366
|
+
const dbPath = join(specRoot, '.runtime', 'sillyspec.db');
|
|
364
367
|
if (!existsSync(dbPath)) {
|
|
365
368
|
const envelope = buildEnvelope({
|
|
366
369
|
command: 'derive',
|