svharness 0.8.0

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.
Files changed (134) hide show
  1. package/README.md +531 -0
  2. package/bin/cli.js +3 -0
  3. package/dist/adapters/_frontmatter.js +24 -0
  4. package/dist/adapters/claude-code.js +12 -0
  5. package/dist/adapters/codechat.js +12 -0
  6. package/dist/adapters/cursor.js +19 -0
  7. package/dist/adapters/generic.js +19 -0
  8. package/dist/adapters/index.js +26 -0
  9. package/dist/adapters/qoder.js +12 -0
  10. package/dist/commands/apply.js +272 -0
  11. package/dist/commands/init.js +420 -0
  12. package/dist/core/agent-injector.js +192 -0
  13. package/dist/core/next-steps.js +91 -0
  14. package/dist/core/render-meta.js +81 -0
  15. package/dist/core/repomix-pack.js +54 -0
  16. package/dist/core/scaffold.js +93 -0
  17. package/dist/core/state.js +80 -0
  18. package/dist/index.js +239 -0
  19. package/dist/types.js +5 -0
  20. package/dist/utils/baseline-copy.js +591 -0
  21. package/dist/utils/baseline-defaults.js +106 -0
  22. package/dist/utils/logger.js +56 -0
  23. package/dist/utils/validate-args.js +132 -0
  24. package/dist/utils/version.js +23 -0
  25. package/dist/wiki/abort.js +30 -0
  26. package/dist/wiki/config.js +79 -0
  27. package/dist/wiki/defaults.js +16 -0
  28. package/dist/wiki/envLoader.js +78 -0
  29. package/dist/wiki/index.js +29 -0
  30. package/dist/wiki/openaiCompat.js +219 -0
  31. package/dist/wiki/repowikiCanonicalSections.js +67 -0
  32. package/dist/wiki/repowikiCheckpoint.js +106 -0
  33. package/dist/wiki/repowikiConfig.js +9 -0
  34. package/dist/wiki/repowikiGit.js +73 -0
  35. package/dist/wiki/repowikiIndexer.js +824 -0
  36. package/dist/wiki/repowikiMarkdownPost.js +123 -0
  37. package/dist/wiki/repowikiMetadataContent.js +64 -0
  38. package/dist/wiki/repowikiMetadataJson.js +15 -0
  39. package/dist/wiki/repowikiScanner.js +156 -0
  40. package/dist/wiki/repowikiStructureNav.js +286 -0
  41. package/dist/wiki/repowikiStructureNormalize.js +218 -0
  42. package/dist/wiki/wikiStructureXml.js +316 -0
  43. package/dist/wiki/wikiTasksWriter.js +127 -0
  44. package/package.json +57 -0
  45. package/templates/_shared/apply-skills/harness-apply-skills-main.md +91 -0
  46. package/templates/_shared/build-rules/harness-build-rule-agent-agnostic.md +35 -0
  47. package/templates/_shared/build-rules/harness-build-rule-chinese-only.md +49 -0
  48. package/templates/_shared/build-rules/harness-build-rule-memory-write.md +31 -0
  49. package/templates/_shared/build-rules/harness-build-rule-orchestrator-flow.md +25 -0
  50. package/templates/_shared/build-rules/harness-build-rule-skills-tasks-output.md +35 -0
  51. package/templates/_shared/build-rules/harness-build-rule-specs-schema.md +32 -0
  52. package/templates/_shared/build-rules/harness-build-rule-user-interaction.md +63 -0
  53. package/templates/_shared/build-skills/harness-build-skill-knowledge-builder.md +120 -0
  54. package/templates/_shared/build-skills/harness-build-skill-orchestrator.md +87 -0
  55. package/templates/_shared/build-skills/harness-build-skill-spec-builder.md +85 -0
  56. package/templates/_shared/build-skills/harness-build-skill-wiki-writer.md +77 -0
  57. package/templates/_shared/meta/AGENTS.md.ejs +53 -0
  58. package/templates/_shared/meta/CHANGELOG.md.ejs +15 -0
  59. package/templates/_shared/meta/README.md.ejs +51 -0
  60. package/templates/_shared/meta/VERSION.ejs +1 -0
  61. package/templates/_shared/meta/harness.yaml.ejs +52 -0
  62. package/templates/_shared/skeleton/agent-env/memory/categories/.gitkeep +1 -0
  63. package/templates/_shared/skeleton/agent-env/memory/inbox/.gitkeep +1 -0
  64. package/templates/_shared/skeleton/agent-env/skills/.gitkeep +1 -0
  65. package/templates/_shared/skeleton/agent-env/tools/.gitkeep +1 -0
  66. package/templates/_shared/skeleton/assets/baseline/code/.gitkeep +1 -0
  67. package/templates/_shared/skeleton/assets/baseline/repomix/.gitkeep +1 -0
  68. package/templates/_shared/skeleton/assets/baseline/wiki/.gitkeep +1 -0
  69. package/templates/_shared/skeleton/assets/raw/.gitkeep +1 -0
  70. package/templates/_shared/skeleton/assets/requirements/.gitkeep +1 -0
  71. package/templates/_shared/skeleton/commands/install/.gitkeep +1 -0
  72. package/templates/_shared/skeleton/commands/update/.gitkeep +1 -0
  73. package/templates/_shared/skeleton/specs/behavior/schema.json +39 -0
  74. package/templates/_shared/skeleton/specs/interfaces/schema.json +38 -0
  75. package/templates/_shared/skeleton/specs/signals/schema.json +37 -0
  76. package/templates/_shared/skeleton/specs/ui/schema.json +44 -0
  77. package/templates/_shared/skeleton/tasks/templates/.gitkeep +0 -0
  78. package/templates/android-compose/skeleton/agent-env/rules/harness-compose-mandatory.mdc +49 -0
  79. package/templates/android-compose/skeleton/agent-env/rules/harness-coroutines-scope.mdc +52 -0
  80. package/templates/android-compose/skeleton/agent-env/rules/harness-hilt-injection.mdc +47 -0
  81. package/templates/android-compose/skeleton/agent-env/rules/harness-mvi-layering.mdc +58 -0
  82. package/templates/android-compose/skeleton/agent-env/skills/harness-android-architecture/SKILL.md +260 -0
  83. package/templates/android-compose/skeleton/agent-env/skills/harness-android-architecture/references/gradle-module-patterns.md +66 -0
  84. package/templates/android-compose/skeleton/agent-env/skills/harness-android-architecture/references/implementation-checklist.md +45 -0
  85. package/templates/android-compose/skeleton/agent-env/skills/harness-android-architecture/references/udf-data-flow.md +80 -0
  86. package/templates/android-compose/skeleton/agent-env/skills/harness-android-cli/SKILL.md +79 -0
  87. package/templates/android-compose/skeleton/agent-env/skills/harness-android-cli/references/interact.md +83 -0
  88. package/templates/android-compose/skeleton/agent-env/skills/harness-android-cli/references/journeys.md +97 -0
  89. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/SKILL.md +162 -0
  90. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/references/canonical-sources.md +116 -0
  91. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/references/diagnostics.md +182 -0
  92. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/references/report-template.md +135 -0
  93. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/references/scoring.md +277 -0
  94. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/references/search-playbook.md +303 -0
  95. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/scripts/compose-reports.init.gradle +58 -0
  96. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-state/SKILL.md +196 -0
  97. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-ui/SKILL.md +192 -0
  98. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-ui/references/composable-api-guide.md +123 -0
  99. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-ui/references/performance-recipes.md +97 -0
  100. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-ui/references/state-patterns.md +93 -0
  101. package/templates/android-compose/skeleton/agent-env/skills/harness-kotlin-coroutines/SKILL.md +167 -0
  102. package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/SKILL.md +45 -0
  103. package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/references/CONFIGURATION.md +44 -0
  104. package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/references/KEEP-RULES-IMPACT-HIERARCHY.md +83 -0
  105. package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/references/REDUNDANT-RULES.md +222 -0
  106. package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/references/REFLECTION-GUIDE.md +139 -0
  107. package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/references/android/topic/performance/app-optimization/enable-app-optimization.md +176 -0
  108. package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/references/android/training/testing/other-components/ui-automator.md +312 -0
  109. package/templates/android-compose/skeleton/agent-env/skills/harness-xml-to-compose/SKILL.md +87 -0
  110. package/templates/android-compose/skeleton/agent-env/skills/harness-xml-to-compose/references/analysis-of-the-project-and-layout.md +42 -0
  111. package/templates/android-compose/skeleton/agent-env/skills/harness-xml-to-compose/references/android/develop/ui/compose/designsystems/migrate-xml-theme-to-compose.md +168 -0
  112. package/templates/android-compose/skeleton/agent-env/skills/harness-xml-to-compose/references/android/develop/ui/compose/setup-compose-dependencies-and-compiler.md +183 -0
  113. package/templates/android-compose/skeleton/agent-env/skills/harness-xml-to-compose/references/identify-optimal-xml-candidate.md +31 -0
  114. package/templates/android-compose/skeleton/agent-env/skills/harness-xml-to-compose/references/xml-layout-migration.md +86 -0
  115. package/templates/android-xml/skeleton/agent-env/rules/seed-aidl-thread.md +29 -0
  116. package/templates/android-xml/skeleton/agent-env/rules/seed-lifecycle-awareness.md +32 -0
  117. package/templates/android-xml/skeleton/agent-env/rules/seed-mvc-layering.md +32 -0
  118. package/templates/android-xml/skeleton/agent-env/rules/seed-view-binding.md +33 -0
  119. package/templates/android-xml/skeleton/agent-env/rules/seed-xml-styling.md +27 -0
  120. package/templates/cpp/skeleton/agent-env/rules/seed-cmake-explicit-sources.md +31 -0
  121. package/templates/cpp/skeleton/agent-env/rules/seed-header-guards.md +34 -0
  122. package/templates/cpp/skeleton/agent-env/rules/seed-include-layering.md +39 -0
  123. package/templates/cpp/skeleton/agent-env/rules/seed-no-cyclic-deps.md +29 -0
  124. package/templates/cpp/skeleton/agent-env/rules/seed-raii.md +30 -0
  125. package/templates/python/skeleton/agent-env/rules/seed-context-managers.md +60 -0
  126. package/templates/python/skeleton/agent-env/rules/seed-docstrings.md +48 -0
  127. package/templates/python/skeleton/agent-env/rules/seed-import-order.md +49 -0
  128. package/templates/python/skeleton/agent-env/rules/seed-pep8-naming.md +45 -0
  129. package/templates/python/skeleton/agent-env/rules/seed-type-annotations.md +43 -0
  130. package/templates/web-react/skeleton/agent-env/rules/seed-controlled-component.md +43 -0
  131. package/templates/web-react/skeleton/agent-env/rules/seed-effect-cleanup.md +43 -0
  132. package/templates/web-react/skeleton/agent-env/rules/seed-hook-rules.md +42 -0
  133. package/templates/web-react/skeleton/agent-env/rules/seed-key-stability.md +39 -0
  134. package/templates/web-react/skeleton/agent-env/rules/seed-no-props-drilling.md +43 -0
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.printNextSteps = printNextSteps;
7
+ const picocolors_1 = __importDefault(require("picocolors"));
8
+ const repomix_pack_1 = require("./repomix-pack");
9
+ /**
10
+ * Print a human-friendly "what to do next" block after init succeeds.
11
+ */
12
+ function printNextSteps(input) {
13
+ const lines = [];
14
+ const wikiMode = input.wikiMode ?? 'off';
15
+ lines.push('');
16
+ lines.push(picocolors_1.default.bold(picocolors_1.default.green('Harness 骨架已生成: ') + input.targetRoot));
17
+ lines.push('');
18
+ lines.push(picocolors_1.default.bold('下一步'));
19
+ lines.push(picocolors_1.default.gray('---------------------------------------------------------'));
20
+ lines.push('');
21
+ lines.push(`1. ${picocolors_1.default.cyan('cd ' + input.targetRoot)}`);
22
+ lines.push('');
23
+ lines.push('2. 确认构建辅助 skill 已加载到 Agent 的 skill 目录: ' +
24
+ picocolors_1.default.cyan(input.adapter.skillsDir + '/'));
25
+ lines.push(' - harness-build-skill-orchestrator (构建总调度)');
26
+ lines.push(' - harness-build-skill-spec-builder (raw → requirements → specs)');
27
+ lines.push(' - harness-build-skill-knowledge-builder (baseline/code 样本 / rules / skills & tasks)');
28
+ lines.push(' - harness-build-skill-wiki-writer (按 TASKS.md 逐页撰写 baseline wiki 正文)');
29
+ lines.push('');
30
+ if (input.adapter.rulesDir) {
31
+ lines.push(' 同时确认 harness 构建规则已注入到: ' +
32
+ picocolors_1.default.cyan(input.adapter.rulesDir + '/'));
33
+ lines.push('');
34
+ }
35
+ if (input.hasSource) {
36
+ lines.push('3. 已识别到基线源码(反向提取模式)。将原始需求文档放入 ' +
37
+ picocolors_1.default.cyan('assets/raw/') +
38
+ ',然后打开 AI 会话并说:');
39
+ }
40
+ else {
41
+ lines.push('3. 全新项目模式。将需求文档编写或粘贴到 ' +
42
+ picocolors_1.default.cyan('assets/raw/') +
43
+ ',然后打开 AI 会话并说:');
44
+ }
45
+ lines.push('');
46
+ lines.push(' ' + picocolors_1.default.bold(picocolors_1.default.yellow('>> 开始构建 harness')));
47
+ lines.push('');
48
+ lines.push('4. 随时用下面的命令查看进度: ' +
49
+ picocolors_1.default.cyan('cat .harness-build-state.yaml'));
50
+ lines.push('');
51
+ if (input.hasSource) {
52
+ lines.push(picocolors_1.default.bold('Baseline Repomix(XML,默认)'));
53
+ lines.push(' 输出文件: ' + picocolors_1.default.cyan((0, repomix_pack_1.repomixPackRelFile)()));
54
+ lines.push(' (若 init 中 Repomix 步骤失败,请查看上方日志中的具体报错)');
55
+ lines.push('');
56
+ }
57
+ if (wikiMode === 'tasks') {
58
+ lines.push(picocolors_1.default.bold('Baseline wiki 任务清单已生成(默认 tasks-only 模式)'));
59
+ lines.push(' 清单位置: ' + picocolors_1.default.cyan('assets/baseline/wiki/TASKS.md'));
60
+ lines.push(' Outline XML: ' + picocolors_1.default.cyan('assets/baseline/wiki/structure.xml'));
61
+ lines.push(' 源码快照: ' + picocolors_1.default.cyan('assets/baseline/code/'));
62
+ lines.push(' 打开 AI 会话并说: ' +
63
+ picocolors_1.default.bold(picocolors_1.default.yellow('>> 按 TASKS.md 逐页生成 baseline wiki')));
64
+ lines.push(' (由 harness-build-skill-wiki-writer skill 接管,源码引用统一指向 assets/baseline/code/)');
65
+ lines.push(' .harness-build-state.yaml 中 P1_wiki 初始为 PENDING,');
66
+ lines.push(' 全部 [x] 后由 harness-build-skill-orchestrator 标记 DONE。');
67
+ lines.push('');
68
+ }
69
+ else if (wikiMode === 'full') {
70
+ lines.push(picocolors_1.default.bold('Baseline wiki 已完整生成(--generate-wiki 模式)'));
71
+ lines.push(' wiki 目录: ' + picocolors_1.default.cyan('assets/baseline/wiki/'));
72
+ lines.push(' .harness-build-state.yaml 中 P1_wiki 初始为 DONE。');
73
+ lines.push('');
74
+ }
75
+ // 更显眼的提示框
76
+ const tipBox = [
77
+ '┌──────────────────────────────────────────────────────────────────────────┐',
78
+ '│ 💡 提示 │',
79
+ '│ svharness 已完成骨架初始化及辅助 skill 的注入到 Agent 的 skill目录 │',
80
+ '│ 自此起所有构建步骤均由 Agent 驱动 │',
81
+ '└──────────────────────────────────────────────────────────────────────────┘',
82
+ ];
83
+ for (const line of tipBox) {
84
+ lines.push(picocolors_1.default.bold(picocolors_1.default.yellow(line)));
85
+ }
86
+ lines.push('');
87
+ for (const line of lines) {
88
+ // eslint-disable-next-line no-console
89
+ console.log(line);
90
+ }
91
+ }
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.renderMeta = renderMeta;
7
+ exports.renderMetaLayered = renderMetaLayered;
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const ejs_1 = __importDefault(require("ejs"));
11
+ const logger_1 = require("../utils/logger");
12
+ const META_FILES = [
13
+ { tplName: 'harness.yaml.ejs', outName: 'harness.yaml' },
14
+ { tplName: 'AGENTS.md.ejs', outName: 'AGENTS.md' },
15
+ { tplName: 'README.md.ejs', outName: 'README.md' },
16
+ { tplName: 'VERSION.ejs', outName: 'VERSION' },
17
+ { tplName: 'CHANGELOG.md.ejs', outName: 'CHANGELOG.md' },
18
+ ];
19
+ /**
20
+ * Render meta-file EJS templates into the harness target root.
21
+ */
22
+ async function renderMeta(metaDir, targetRoot, ctx) {
23
+ if (!(await fs_extra_1.default.pathExists(metaDir))) {
24
+ throw new Error(`meta template directory not found: ${metaDir}`);
25
+ }
26
+ const written = [];
27
+ for (const { tplName, outName } of META_FILES) {
28
+ const tplPath = node_path_1.default.join(metaDir, tplName);
29
+ if (!(await fs_extra_1.default.pathExists(tplPath))) {
30
+ logger_1.logger.warn(`meta template missing, skipped: ${tplName}`);
31
+ continue;
32
+ }
33
+ const tplContent = await fs_extra_1.default.readFile(tplPath, 'utf8');
34
+ const rendered = ejs_1.default.render(tplContent, ctx, { async: false });
35
+ const outPath = node_path_1.default.join(targetRoot, outName);
36
+ await fs_extra_1.default.outputFile(outPath, rendered, 'utf8');
37
+ written.push(outName);
38
+ logger_1.logger.debug(`rendered meta: ${outName}`);
39
+ }
40
+ return written;
41
+ }
42
+ /**
43
+ * Layered meta rendering: given multiple meta template directories (ordered
44
+ * low→high priority, e.g. `[_shared/meta, <arch>/meta]`), for each output
45
+ * file the LAST directory that contains the template wins.
46
+ *
47
+ * Missing directories are tolerated; at least one must contain each tpl.
48
+ */
49
+ async function renderMetaLayered(metaDirs, targetRoot, ctx) {
50
+ const existing = [];
51
+ for (const d of metaDirs) {
52
+ if (await fs_extra_1.default.pathExists(d))
53
+ existing.push(d);
54
+ }
55
+ if (existing.length === 0) {
56
+ throw new Error(`no meta template directory found among: ${metaDirs.join(', ')}`);
57
+ }
58
+ const written = [];
59
+ for (const { tplName, outName } of META_FILES) {
60
+ // Walk directories in reverse so the highest-priority one wins.
61
+ let picked = null;
62
+ for (let i = existing.length - 1; i >= 0; i--) {
63
+ const cand = node_path_1.default.join(existing[i], tplName);
64
+ if (await fs_extra_1.default.pathExists(cand)) {
65
+ picked = cand;
66
+ break;
67
+ }
68
+ }
69
+ if (!picked) {
70
+ logger_1.logger.warn(`meta template missing in all layers, skipped: ${tplName}`);
71
+ continue;
72
+ }
73
+ const tplContent = await fs_extra_1.default.readFile(picked, 'utf8');
74
+ const rendered = ejs_1.default.render(tplContent, ctx, { async: false });
75
+ const outPath = node_path_1.default.join(targetRoot, outName);
76
+ await fs_extra_1.default.outputFile(outPath, rendered, 'utf8');
77
+ written.push(outName);
78
+ logger_1.logger.debug(`rendered meta: ${outName} (from ${picked})`);
79
+ }
80
+ return written;
81
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.REPOMIX_PACK_FILENAME = exports.REPOMIX_BASELINE_REL_DIR = void 0;
7
+ exports.repomixPackRelFile = repomixPackRelFile;
8
+ exports.runRepomixPackBaseline = runRepomixPackBaseline;
9
+ const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const repomix_1 = require("repomix");
12
+ /** Relative to harness root: `assets/baseline/repomix/repomix-pack.xml`. */
13
+ exports.REPOMIX_BASELINE_REL_DIR = node_path_1.default.join('assets', 'baseline', 'repomix');
14
+ exports.REPOMIX_PACK_FILENAME = 'repomix-pack.xml';
15
+ function repomixPackRelFile() {
16
+ return node_path_1.default.join(exports.REPOMIX_BASELINE_REL_DIR, exports.REPOMIX_PACK_FILENAME).replace(/\\/g, '/');
17
+ }
18
+ /**
19
+ * Pack `packRootFs` with Repomix (library `runCli`) and write XML to
20
+ * `<harnessRootFs>/assets/baseline/repomix/repomix-pack.xml`.
21
+ */
22
+ async function runRepomixPackBaseline(opts) {
23
+ const { packRootFs, harnessRootFs, onLog } = opts;
24
+ const outDir = node_path_1.default.join(harnessRootFs, exports.REPOMIX_BASELINE_REL_DIR);
25
+ await fs_extra_1.default.ensureDir(outDir);
26
+ const outAbs = node_path_1.default.join(outDir, exports.REPOMIX_PACK_FILENAME);
27
+ await fs_extra_1.default.remove(outAbs).catch(() => undefined);
28
+ const cliOptions = {
29
+ style: 'xml',
30
+ output: outAbs,
31
+ quiet: true,
32
+ compress: true,
33
+ parsableStyle: true,
34
+ outputShowLineNumbers: true,
35
+ truncateBase64: true,
36
+ };
37
+ onLog?.(`[repomix] cwd=${packRootFs}`);
38
+ onLog?.(`[repomix] output=${outAbs}`);
39
+ try {
40
+ const result = await (0, repomix_1.runCli)(['.'], packRootFs, cliOptions);
41
+ if (result && typeof result === 'object' && 'packResult' in result) {
42
+ const pr = result.packResult;
43
+ onLog?.(`[repomix] done: ${pr.totalFiles} files, ${pr.totalTokens} tokens, ${pr.totalCharacters} chars`);
44
+ }
45
+ }
46
+ catch (e) {
47
+ const err = e;
48
+ throw new Error(`repomix 失败:${err.message}`);
49
+ }
50
+ if (!(await fs_extra_1.default.pathExists(outAbs))) {
51
+ throw new Error(`repomix 未写出预期文件:${outAbs}`);
52
+ }
53
+ return outAbs;
54
+ }
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.scaffold = scaffold;
7
+ exports.scaffoldLayered = scaffoldLayered;
8
+ exports.rollbackTarget = rollbackTarget;
9
+ exports.ensureTargetAvailable = ensureTargetAvailable;
10
+ const fs_extra_1 = __importDefault(require("fs-extra"));
11
+ const node_path_1 = __importDefault(require("node:path"));
12
+ const logger_1 = require("../utils/logger");
13
+ /**
14
+ * Recursively copy a skeleton directory tree to the target root.
15
+ * Preserves .gitkeep placeholders so empty directories survive.
16
+ */
17
+ async function scaffold(skeletonSrc, targetRoot) {
18
+ if (!(await fs_extra_1.default.pathExists(skeletonSrc))) {
19
+ throw new Error(`skeleton template not found: ${skeletonSrc}`);
20
+ }
21
+ await fs_extra_1.default.ensureDir(targetRoot);
22
+ const copied = [];
23
+ await fs_extra_1.default.copy(skeletonSrc, targetRoot, {
24
+ overwrite: true,
25
+ errorOnExist: false,
26
+ filter: (src) => {
27
+ // Track every real file we're about to copy (skeleton dir itself has empty base).
28
+ const rel = node_path_1.default.relative(skeletonSrc, src);
29
+ if (rel && rel.length > 0) {
30
+ try {
31
+ const stat = fs_extra_1.default.statSync(src);
32
+ if (stat.isFile()) {
33
+ copied.push(rel);
34
+ }
35
+ }
36
+ catch {
37
+ /* ignore transient fs errors */
38
+ }
39
+ }
40
+ return true;
41
+ },
42
+ });
43
+ logger_1.logger.debug(`scaffold copied ${copied.length} file(s) to ${targetRoot}`);
44
+ return copied;
45
+ }
46
+ /**
47
+ * Layered scaffold: copy multiple skeleton sources in order, later sources
48
+ * overwrite earlier ones on conflict. Use for `_shared` + `<arch>` merging.
49
+ *
50
+ * Missing sources are skipped with a warning (not an error), so an arch
51
+ * without arch-specific skeleton files still works.
52
+ */
53
+ async function scaffoldLayered(skeletonSrcs, targetRoot) {
54
+ const all = [];
55
+ let anyFound = false;
56
+ for (const src of skeletonSrcs) {
57
+ if (!(await fs_extra_1.default.pathExists(src))) {
58
+ logger_1.logger.debug(`scaffold layer skipped (missing): ${src}`);
59
+ continue;
60
+ }
61
+ anyFound = true;
62
+ const copied = await scaffold(src, targetRoot);
63
+ all.push(...copied);
64
+ }
65
+ if (!anyFound) {
66
+ throw new Error(`no skeleton source found among: ${skeletonSrcs.join(', ')}`);
67
+ }
68
+ return all;
69
+ }
70
+ /**
71
+ * Rollback helper: remove a target directory (best-effort, ignores ENOENT).
72
+ */
73
+ async function rollbackTarget(targetRoot) {
74
+ try {
75
+ await fs_extra_1.default.remove(targetRoot);
76
+ logger_1.logger.debug(`rolled back ${targetRoot}`);
77
+ }
78
+ catch (err) {
79
+ logger_1.logger.warn(`rollback failed for ${targetRoot}: ${err.message}`);
80
+ }
81
+ }
82
+ /**
83
+ * Target directory guard. When targetRoot exists and `force` is not set, throws.
84
+ */
85
+ async function ensureTargetAvailable(targetRoot, force) {
86
+ if (!(await fs_extra_1.default.pathExists(targetRoot)))
87
+ return;
88
+ if (!force) {
89
+ throw new Error(`target directory already exists: ${targetRoot}\n Use --force to overwrite (will delete existing content).`);
90
+ }
91
+ logger_1.logger.warn(`--force specified, removing existing ${targetRoot}`);
92
+ await fs_extra_1.default.remove(targetRoot);
93
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.buildInitialState = buildInitialState;
7
+ exports.writeStateFile = writeStateFile;
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const js_yaml_1 = __importDefault(require("js-yaml"));
11
+ const logger_1 = require("../utils/logger");
12
+ /**
13
+ * Build the initial state object. P-1 is DONE (skeleton just created);
14
+ * everything else starts PENDING. `P1_wiki` is conditional on
15
+ * `wikiPhase` (only present when project has baseline).
16
+ *
17
+ * Phase order: P-1_skeleton → [P1_wiki] → P2_raw → P3_requirements → P4_specs
18
+ * → P5_rules → P6_skills_tasks → P7_memory → P-Final_seal
19
+ *
20
+ * Design: P1_wiki comes before P2_raw because baseline wiki is the project
21
+ * experience foundation — it provides architectural knowledge that helps the
22
+ * user write better requirement documents. When no baseline, P1_wiki is
23
+ * absent and the flow starts from P2_raw.
24
+ */
25
+ function buildInitialState(input) {
26
+ const phases = {
27
+ 'P-1_skeleton': { status: 'DONE' },
28
+ };
29
+ const wp = input.wikiPhase ?? 'none';
30
+ // P1_wiki: baseline wiki (project experience foundation) — only when baseline exists
31
+ if (wp === 'done') {
32
+ phases.P1_wiki = { status: 'DONE', requires_agent: false, mode: 'full', source: 'cli-full' };
33
+ }
34
+ else if (wp === 'pending') {
35
+ phases.P1_wiki = {
36
+ status: 'PENDING',
37
+ requires_agent: true,
38
+ mode: 'outline-only',
39
+ source: input.wikiSource ?? 'cli-tasks',
40
+ tasks_file: 'assets/baseline/wiki/TASKS.md',
41
+ };
42
+ }
43
+ // P2_raw: user adds requirement documents (can reference wiki for context)
44
+ phases.P2_raw = { status: 'PENDING', manual_required: true };
45
+ phases.P3_requirements = { status: 'PENDING', requires_agent: true };
46
+ phases.P4_specs = { status: 'PENDING', requires_agent: true };
47
+ phases.P5_rules = { status: 'PENDING', manual_required: true };
48
+ phases.P6_skills_tasks = { status: 'PENDING', requires_agent: true };
49
+ phases.P7_memory = { status: 'PENDING', requires_agent: true };
50
+ phases['P-Final_seal'] = { status: 'PENDING' };
51
+ // current_phase starts at P1_wiki (if present) or P2_raw (if no baseline)
52
+ const startPhase = wp !== 'none' ? 'P1_wiki' : 'P2_raw';
53
+ const startAction = wp !== 'none'
54
+ ? '请先构建 baseline wiki(项目经验底座),完成后继续下一步。'
55
+ : '请将原始需求文档放入 assets/raw/ 目录后,告诉 Agent 继续构建。';
56
+ const state = {
57
+ project: input.name,
58
+ arch: input.arch,
59
+ agent: input.agent,
60
+ baseline: input.baseline,
61
+ created_at: input.createdAt,
62
+ svharnessbuild_version: input.cliVersion,
63
+ current_phase: startPhase,
64
+ phases,
65
+ next_action: startAction,
66
+ };
67
+ return state;
68
+ }
69
+ /**
70
+ * Write .harness-build-state.yaml under the target root.
71
+ */
72
+ async function writeStateFile(targetRoot, state) {
73
+ const outPath = node_path_1.default.join(targetRoot, '.harness-build-state.yaml');
74
+ const body = js_yaml_1.default.dump(state, { lineWidth: 100, noRefs: true, sortKeys: false });
75
+ const header = '# svharness build-state tracker (auto-generated; AI Agent may update).\n' +
76
+ '# Phase legend: PENDING | IN_PROGRESS | DONE | FAILED | BLOCKED\n';
77
+ await fs_extra_1.default.outputFile(outPath, header + body, 'utf8');
78
+ logger_1.logger.debug(`wrote state file: ${outPath}`);
79
+ return outPath;
80
+ }
package/dist/index.js ADDED
@@ -0,0 +1,239 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const commander_1 = require("commander");
4
+ const logger_1 = require("./utils/logger");
5
+ const version_1 = require("./utils/version");
6
+ const validate_args_1 = require("./utils/validate-args");
7
+ const init_1 = require("./commands/init");
8
+ const apply_1 = require("./commands/apply");
9
+ /**
10
+ * Default values surfaced to users (also reflected in README "全部参数" tables).
11
+ *
12
+ * Keep this list in one place so README, banner help text and command options
13
+ * stay in sync. Updating a default here is a *single* source of truth change.
14
+ */
15
+ const DEFAULT_ARCH = 'android-compose';
16
+ const DEFAULT_AGENT = 'codechat';
17
+ /**
18
+ * Resolve `--harness-name` / `--name` (alias) into a single value and emit a
19
+ * deprecation warning when only the legacy `--name` form is used.
20
+ */
21
+ function resolveHarnessName(opts) {
22
+ if (opts.harnessName && opts.name && opts.harnessName !== opts.name) {
23
+ throw new Error('同时收到 --harness-name 与 --name 但取值不同,请只保留一个(推荐 --harness-name)');
24
+ }
25
+ if (opts.harnessName)
26
+ return opts.harnessName;
27
+ if (opts.name) {
28
+ logger_1.logger.warn('⚠️ --name 已重命名为 --harness-name,旧名将在下个 minor 版本移除。');
29
+ return opts.name;
30
+ }
31
+ throw new Error('--harness-name <name> 是必填参数');
32
+ }
33
+ async function runBuildAction(opts) {
34
+ try {
35
+ const harnessName = resolveHarnessName(opts);
36
+ await (0, init_1.runInit)({
37
+ name: harnessName,
38
+ arch: opts.arch,
39
+ agent: opts.agent,
40
+ baseline: opts.baseline,
41
+ baselineBranch: opts.baselineBranch,
42
+ baselineMaxFileKB: opts.baselineMaxFileKb,
43
+ force: !!opts.force,
44
+ yes: !!opts.yes,
45
+ verbose: !!opts.verbose,
46
+ generateWiki: !!opts.generateWiki,
47
+ wikiTasksOnly: !!opts.wikiTasksOnly,
48
+ wikiLang: opts.wikiLang === 'en' ? 'en' : opts.wikiLang === 'zh' ? 'zh' : undefined,
49
+ wikiModel: opts.wikiModel,
50
+ wikiBaseUrl: opts.wikiBaseUrl,
51
+ wikiApiKey: opts.wikiApiKey,
52
+ wikiSource: opts.wikiSource,
53
+ });
54
+ }
55
+ catch (err) {
56
+ logger_1.logger.error(err.message);
57
+ process.exitCode = 1;
58
+ }
59
+ }
60
+ /**
61
+ * Attach all options shared between `build` and the legacy `init` alias.
62
+ * Centralised so the two stay byte-for-byte identical.
63
+ */
64
+ function attachBuildOptions(cmd) {
65
+ return cmd
66
+ .option('--harness-name <name>', '【必填】harness 名称(小写字母、数字、中连线),生成 <name>-harness/ 目录')
67
+ .option('--name <name>', '【已废弃】--harness-name 的旧别名,下个 minor 版本移除')
68
+ .option('--arch <arch>', '架构模板:' + (0, validate_args_1.listSupportedArches)().join(' | '), DEFAULT_ARCH)
69
+ .option('--agent <agent>', '目标 Agent:' + (0, validate_args_1.listSupportedAgents)().join(' | '), DEFAULT_AGENT)
70
+ .option('--baseline <path|url>', '【可选】基线源码路径或 Git 仓库地址')
71
+ .option('--baseline-branch <name>', '【Git 模式】指定分支名(默认:main)')
72
+ .option('--baseline-max-file-kb <kb>', '【过滤】单文件大小上限 KB(默认:1024)', (v) => {
73
+ const n = Number(v);
74
+ if (!Number.isFinite(n)) {
75
+ throw new Error(`--baseline-max-file-kb 必须是数字,收到: ${v}`);
76
+ }
77
+ return n;
78
+ })
79
+ .option('--force', '强制覆盖已存在的目标目录')
80
+ .option('-y, --yes', '跳过交互确认,使用默认值')
81
+ .option('--verbose', '显示详细日志')
82
+ .option('--generate-wiki', '【Wiki】自动生成完整 Wiki 文档(需 --baseline)')
83
+ .option('--wiki-tasks-only', '【Wiki】仅生成 Wiki 大纲和任务列表,由 Agent 后续撰写(有 --baseline 时为隐式默认)')
84
+ .option('--wiki-lang <lang>', '【Wiki】输出语言:zh(默认)| en')
85
+ .option('--wiki-model <model>', '【Wiki】AI 模型名称(覆写默认配置)')
86
+ .option('--wiki-base-url <url>', '【Wiki】OpenAI 兼容 API 地址')
87
+ .option('--wiki-api-key <key>', '【Wiki】API 密钥')
88
+ .option('--wiki-source <path>', '【Wiki】扫描的源码根路径(默认:--baseline 或当前目录)');
89
+ }
90
+ function main() {
91
+ const program = new commander_1.Command();
92
+ const version = (0, version_1.getCliVersion)();
93
+ program
94
+ .name('svharness')
95
+ .description(`规格驱动开发(SDD)AI 协作框架脚手架工具`)
96
+ .version(version, '-v, --version', '打印版本号')
97
+ .helpOption('-h, --help', '显示帮助信息');
98
+ const banner = [
99
+ '',
100
+ '╔══════════════════════════════════════════════════════════════════════════╗',
101
+ '║ svharness v' + version.padEnd(33) + '║',
102
+ '╠══════════════════════════════════════════════════════════════════════════╣',
103
+ '║ 规格驱动开发(SDD)AI 协作框架脚手架 ║',
104
+ '║ 为任意 Agent IDE 生成项目本地知识层骨架(harness) ║',
105
+ '╠══════════════════════════════════════════════════════════════════════════╣',
106
+ '║ 支持架构:android-compose | android-xml | cpp | web-react | python ║',
107
+ '║ 支持 Agent:codechat | qoder | cursor | claude-code | generic ║',
108
+ '║ 运行要求:Node.js ≥ 18 ║',
109
+ '╚══════════════════════════════════════════════════════════════════════════╝',
110
+ '',
111
+ ].join('\n');
112
+ program.addHelpText('beforeAll', banner);
113
+ program.addHelpText('afterAll', [
114
+ '',
115
+ '┌──────────────────────────────────────────────────────────────────────────┐',
116
+ '│ 📖 快速上手 │',
117
+ '├──────────────────────────────────────────────────────────────────────────┤',
118
+ '│ │',
119
+ '│ # 方式一:交互式创建(仅 --harness-name 必填,其余取默认值) │',
120
+ '│ svharness build --harness-name my-app │',
121
+ '│ │',
122
+ '│ # 方式二:本地基线 + 自动生成 wiki │',
123
+ '│ svharness build \\ │',
124
+ '│ --harness-name my-app \\ │',
125
+ '│ --baseline your_project_dir \\ │',
126
+ '│ --generate-wiki \\ │',
127
+ '│ --yes │',
128
+ '│ │',
129
+ '│ # 方式三:Git 基线(指定分支与单文件上限) │',
130
+ '│ svharness build \\ │',
131
+ '│ --harness-name my-app \\ │',
132
+ '│ --baseline git@github.com:your/repo.git \\ │',
133
+ '│ --baseline-branch main \\ │',
134
+ '│ --baseline-max-file-kb 1024 \\ │',
135
+ '│ --generate-wiki │',
136
+ '│ │',
137
+ '│ # 方式四:把已构建好的 harness 绑定到目标项目 │',
138
+ '│ svharness apply \\ │',
139
+ '│ --harness ./my-app-harness \\ │',
140
+ '│ --target . \\ │',
141
+ '│ --yes │',
142
+ '│ │',
143
+ '└──────────────────────────────────────────────────────────────────────────┘',
144
+ '',
145
+ '┌──────────────────────────────────────────────────────────────────────────┐',
146
+ '│ 📋 build 参数说明(含默认值) │',
147
+ '├──────────────────────────────────────────────────────────────────────────┤',
148
+ '│ │',
149
+ '│ 必填参数: │',
150
+ '│ --harness-name <name> harness 名称(小写字母、数字、中连线) │',
151
+ '│ │',
152
+ '│ 含默认值参数: │',
153
+ '│ --arch <架构> 默认:android-compose │',
154
+ '│ --agent <Agent> 默认:codechat │',
155
+ '│ --wiki-lang <语言> 默认:zh │',
156
+ '│ --baseline-branch 默认:main(仅 git 模式) │',
157
+ '│ --baseline-max-file-kb 默认:1024 │',
158
+ '│ │',
159
+ '│ 可选开关: │',
160
+ '│ --baseline <路径|URL> 本地目录或 Git 仓库地址 │',
161
+ '│ --generate-wiki 自动生成完整 Wiki │',
162
+ '│ --wiki-tasks-only 仅生成 Wiki 大纲(有 --baseline 时为隐式默认) │',
163
+ '│ --force 覆盖已存在的目标目录 │',
164
+ '│ -y, --yes 跳过交互确认 │',
165
+ '│ --verbose 显示详细日志 │',
166
+ '│ │',
167
+ '└──────────────────────────────────────────────────────────────────────────┘',
168
+ '',
169
+ '┌──────────────────────────────────────────────────────────────────────────┐',
170
+ '│ 📋 apply 参数说明(含默认值) │',
171
+ '├──────────────────────────────────────────────────────────────────────────┤',
172
+ '│ │',
173
+ '│ 必填参数: │',
174
+ '│ --harness <path> 已构建好的 harness 目录路径 │',
175
+ '│ │',
176
+ '│ 含默认值参数: │',
177
+ '│ --target <path> 默认:当前工作目录(cwd) │',
178
+ '│ --agent <agent> 默认:从 <harness>/.harness-build-state.yaml 读取│',
179
+ '│ │',
180
+ '│ 可选开关: │',
181
+ '│ --clone 完整拷贝 harness 到目标项目(默认 bind-only) │',
182
+ '│ --force 覆盖已存在的 dispatcher skill 目录 │',
183
+ '│ -y, --yes 跳过交互确认 │',
184
+ '│ --verbose 显示详细日志 │',
185
+ '│ │',
186
+ '└──────────────────────────────────────────────────────────────────────────┘',
187
+ '',
188
+ ' 📚 完整文档:https://yesv-desaysv.feishu.cn/docx/YEIHdD4NGooMdrxQO4McqwfNnjf?pre_pathname=%2Fdrive%2Fhome%2F',
189
+ '',
190
+ ].join('\n'));
191
+ // ── build:主命令(v0.8+) ───────────────────────────────────────────────
192
+ attachBuildOptions(program
193
+ .command('build')
194
+ .description('构建 harness:生成目录骨架、渲染元文件、向对应 Agent 注入构建辅助 skill。')).action(async (opts) => {
195
+ await runBuildAction(opts);
196
+ });
197
+ // ── init:隐藏别名(兼容老脚本,下个 minor 版本移除) ─────────────────────
198
+ attachBuildOptions(program
199
+ .command('init', { hidden: true })
200
+ .description('【已废弃别名】等同于 `build`,下个 minor 版本移除,请改用 `svharness build`。')).action(async (opts) => {
201
+ logger_1.logger.warn('⚠️ `svharness init` 已重命名为 `svharness build`,旧名将在下个 minor 版本移除。');
202
+ await runBuildAction(opts);
203
+ });
204
+ program
205
+ .command('apply')
206
+ .description('把已构建好的 harness(<name>-harness/)绑定到目标项目:向目标项目的 agent skills 目录注入单一 dispatcher skill(harness-apply-skills-main),binding 元数据直接内联到 SKILL.md 的「## 绑定元数据」小节作为运行时权威来源;references/binding.yaml 同时写出作为冗余副本供工具链解析。')
207
+ .requiredOption('--harness <path>', '已构建好的 harness 目录路径(形如 ./my-app-harness)')
208
+ .option('--target <path>', '目标项目根目录(默认:当前工作目录)')
209
+ .option('--agent <agent>', '目标 Agent:' +
210
+ (0, validate_args_1.listSupportedAgents)().join(' | ') +
211
+ '(省略时从 harness/.harness-build-state.yaml 读取)')
212
+ .option('--force', '覆盖已存在的 dispatcher skill 目录')
213
+ .option('--clone', '把整个 harness 目录拷贝到目标项目根下(默认仅绑定,不拷贝)')
214
+ .option('-y, --yes', '跳过交互确认')
215
+ .option('--verbose', '显示详细日志')
216
+ .action(async (opts) => {
217
+ try {
218
+ await (0, apply_1.runApply)({
219
+ harness: opts.harness,
220
+ target: opts.target,
221
+ agent: opts.agent,
222
+ force: !!opts.force,
223
+ clone: !!opts.clone,
224
+ yes: !!opts.yes,
225
+ verbose: !!opts.verbose,
226
+ });
227
+ }
228
+ catch (err) {
229
+ logger_1.logger.error(err.message);
230
+ process.exitCode = 1;
231
+ }
232
+ });
233
+ // Parse argv. Use process.argv directly so --help and --version work as-is.
234
+ program.parseAsync(process.argv).catch((err) => {
235
+ logger_1.logger.error(err.message);
236
+ process.exitCode = 1;
237
+ });
238
+ }
239
+ main();
package/dist/types.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ /**
3
+ * Public types shared across the CLI.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });