sillyspec 3.26.7 → 3.26.8

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/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 (specDir && existsSync(legacyDir)) {
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
- const ignoreRules = ['.sillyspec/codebase/SCAN-RAW.md', '.sillyspec/local.yaml', '.sillyspec/.runtime/'];
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,7 +466,7 @@ 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
 
@@ -509,7 +566,8 @@ export async function cmdInit(projectDir, options = {}) {
509
566
  }
510
567
 
511
568
  console.log('');
512
- await doInstall(projectDir, selectedTools, subprojects, resolvedSpecDir);
569
+ await doInstall(projectDir, selectedTools, subprojects, resolvedSpecDir, { noSkills, platformMode: platformOpts != null });
570
+ writeInitPlatformPointer(projectDir, resolvedSpecDir, platformOpts);
513
571
  showSummary(version, selectedTools, resolvedSpecDir);
514
572
  return;
515
573
  }
@@ -517,18 +575,27 @@ export async function cmdInit(projectDir, options = {}) {
517
575
  // ── 默认快速模式:检测 → 安装 → 结束 ──
518
576
 
519
577
  let tools = [];
520
- if (tool) {
521
- if (!VALID_TOOLS.includes(tool)) {
522
- console.error(`❌ 未知工具: ${tool}`);
523
- console.error(`支持的工具: ${VALID_TOOLS.join(', ')}`);
524
- process.exit(1);
578
+ // --tool 多值(tools 数组,index.js 解析逗号分隔/重复 flag 收集)优先;
579
+ // 兼容旧 tool 单值(程序化调用)。两者都校验 VALID_TOOLS 并合并去重(保序)。
580
+ const requested = [
581
+ ...(Array.isArray(toolsOpt) && toolsOpt.length > 0 ? toolsOpt : []),
582
+ ...(tool ? [tool] : []),
583
+ ];
584
+ if (requested.length > 0) {
585
+ for (const t of requested) {
586
+ if (!VALID_TOOLS.includes(t)) {
587
+ console.error(`❌ 未知工具: ${t}`);
588
+ console.error(`支持的工具: ${VALID_TOOLS.join(', ')}`);
589
+ process.exit(1);
590
+ }
525
591
  }
526
- tools = [tool];
592
+ tools = [...new Set(requested)];
527
593
  } else {
528
594
  tools = detectTools(projectDir);
529
595
  }
530
596
 
531
- await doInstall(projectDir, tools, [], resolvedSpecDir);
597
+ await doInstall(projectDir, tools, [], resolvedSpecDir, { noSkills, platformMode: platformOpts != null });
598
+ writeInitPlatformPointer(projectDir, resolvedSpecDir, platformOpts);
532
599
 
533
600
  console.log('');
534
601
  console.log(chalk.green(` ✅ SillySpec v${version} 安装完成!`));