@colin4k1024/tsp 2.4.0 → 2.4.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.
@@ -115,7 +115,7 @@ function buildDocFreshnessCheck(root) {
115
115
  id: 'validate-doc-freshness',
116
116
  label: 'validate-doc-freshness',
117
117
  command: 'node scripts/validate-doc-freshness.js',
118
- status: report.errorCount > 0 ? 'fail' : (report.warningCount > 0 ? 'warn' : 'pass'),
118
+ status: report.errorCount > 0 ? 'fail' : 'pass',
119
119
  details: report.errorCount > 0
120
120
  ? report.errors[0]
121
121
  : (report.warningCount > 0 ? report.warnings[0] : `checked ${report.markdownFileCount} markdown files`),
@@ -148,20 +148,60 @@ function buildPrebuiltCheck(root) {
148
148
  };
149
149
  }
150
150
 
151
+ function packCurrentTarball(root) {
152
+ const result = spawnSync('npm', ['pack', '--json', '--ignore-scripts'], {
153
+ cwd: root,
154
+ encoding: 'utf8',
155
+ });
156
+
157
+ if (result.status !== 0) {
158
+ throw new Error(summarizeOutput(result.stderr || result.stdout) || `npm pack failed with exit ${result.status}`);
159
+ }
160
+
161
+ let data;
162
+ try {
163
+ data = JSON.parse(result.stdout);
164
+ } catch (error) {
165
+ throw new Error(`Unable to parse npm pack JSON output: ${error.message}`);
166
+ }
167
+
168
+ if (!Array.isArray(data) || !data[0] || typeof data[0].filename !== 'string' || data[0].filename.length === 0) {
169
+ throw new Error('npm pack JSON output is missing a filename entry.');
170
+ }
171
+
172
+ const tarballPath = path.join(root, data[0].filename);
173
+ return {
174
+ tarballPath,
175
+ cleanup() {
176
+ fs.rmSync(tarballPath, { force: true });
177
+ },
178
+ };
179
+ }
180
+
151
181
  function buildTarballCheck(options) {
182
+ let cleanup = null;
152
183
  try {
153
- const tarballPath = resolveTarballPath({
154
- root: options.root,
155
- tarball: options.tarball,
156
- packJson: options.packJson,
157
- });
184
+ const usingExplicitInput = Boolean(options.tarball || options.packJson);
185
+ const tarballPath = usingExplicitInput
186
+ ? resolveTarballPath({
187
+ root: options.root,
188
+ tarball: options.tarball,
189
+ packJson: options.packJson,
190
+ })
191
+ : (() => {
192
+ const packed = packCurrentTarball(options.root);
193
+ cleanup = packed.cleanup;
194
+ return packed.tarballPath;
195
+ })();
158
196
  const result = validateTarball(tarballPath, validatePrebuilt({ root: options.root }).platforms);
159
197
  return {
160
198
  id: 'validate-packed-tarball',
161
199
  label: 'validate-packed-tarball',
162
200
  command: options.packJson
163
201
  ? `node scripts/validate-packed-tarball.js --pack-json ${path.relative(options.root, options.packJson)}`
164
- : `node scripts/validate-packed-tarball.js --tarball ${path.relative(options.root, tarballPath)}`,
202
+ : options.tarball
203
+ ? `node scripts/validate-packed-tarball.js --tarball ${path.relative(options.root, tarballPath)}`
204
+ : `npm pack --json --ignore-scripts && node scripts/validate-packed-tarball.js --tarball ${path.relative(options.root, tarballPath)}`,
165
205
  status: result.missing.length > 0 ? 'fail' : 'pass',
166
206
  details: result.missing.length > 0
167
207
  ? result.missing[0]
@@ -187,6 +227,8 @@ function buildTarballCheck(options) {
187
227
  status: 'skip',
188
228
  details: message,
189
229
  };
230
+ } finally {
231
+ cleanup?.();
190
232
  }
191
233
  }
192
234
 
@@ -65,7 +65,7 @@ if [[ "$PUBLISH" == "true" ]]; then
65
65
  echo " Size: $(( TARBALL_SIZE / 1024 / 1024 ))MB (${TARBALL_SIZE} bytes)"
66
66
 
67
67
  echo ""
68
- echo "🚀 Publishing @colin4k1024/tsp-create@${NEW_VERSION} to npm ..."
68
+ echo "🚀 Publishing @colin4k1024/tsp@${NEW_VERSION} to npm ..."
69
69
  npm publish "${TARBALL}" --access public
70
70
  echo "✅ Published to npm."
71
71
 
@@ -59,6 +59,25 @@ function parseArgs(argv) {
59
59
  return options;
60
60
  }
61
61
 
62
+ function expectedTarballFilenameForRoot(root) {
63
+ const packageJsonPath = path.join(root, 'package.json');
64
+ if (!fs.existsSync(packageJsonPath)) {
65
+ return null;
66
+ }
67
+
68
+ try {
69
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
70
+ if (typeof pkg.name !== 'string' || pkg.name.length === 0 || typeof pkg.version !== 'string' || pkg.version.length === 0) {
71
+ return null;
72
+ }
73
+
74
+ const normalizedName = pkg.name.replace(/^@/, '').replace(/\//g, '-');
75
+ return `${normalizedName}-${pkg.version}.tgz`;
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+
62
81
  function resolveTarballPath(options) {
63
82
  if (options.tarball) {
64
83
  return options.tarball;
@@ -92,6 +111,11 @@ function resolveTarballPath(options) {
92
111
  throw new Error(`No .tgz tarball found under ${options.root}; pass --tarball or --pack-json explicitly.`);
93
112
  }
94
113
 
114
+ const expectedTarballFilename = expectedTarballFilenameForRoot(options.root);
115
+ if (expectedTarballFilename && candidates.includes(expectedTarballFilename)) {
116
+ return path.join(options.root, expectedTarballFilename);
117
+ }
118
+
95
119
  return path.join(options.root, candidates[0]);
96
120
  }
97
121
 
@@ -142,6 +166,7 @@ if (require.main === module) {
142
166
  }
143
167
 
144
168
  module.exports = {
169
+ expectedTarballFilenameForRoot,
145
170
  main,
146
171
  parseArgs,
147
172
  resolveTarballPath,
@@ -399,7 +399,7 @@ function analyzeTask(options) {
399
399
  result.reason = routeReason(catalog, '/team-intake', '未检测到任务 artifact 目录,主链应从 intake 开始。');
400
400
  result.missingPrerequisites.push('缺少 docs/artifacts/{YYYY-MM-DD}-{slug}/ 任务目录');
401
401
  if (brownfieldRepo) {
402
- result.brownfieldSuggestions.push('既有项目建议先执行 /update-codemaps,再进入 /team-plan 补齐 Brownfield Context Snapshot。');
402
+ result.brownfieldSuggestions.push('既有项目建议先执行 /update-codemaps;需要轻量结构证据时用 Graphify,需要跨模块影响面或 MCP 证据时用 GitNexus,再进入 /team-plan 补齐 Brownfield Context Snapshot。');
403
403
  }
404
404
  if (!projectContext.exists || projectContext.missingSections.length > 0) {
405
405
  addProjectContextRemediation(result, null);
@@ -443,7 +443,7 @@ function analyzeTask(options) {
443
443
  result.reason = routeReason(catalog, '/team-plan', '已有 PRD,但尚未形成 Delivery Plan。');
444
444
  result.missingPrerequisites.push('缺少 delivery-plan.md');
445
445
  if (brownfieldRepo) {
446
- result.brownfieldSuggestions.push('如果是 brownfield 任务,在 delivery-plan.md 中补齐 Brownfield Context Snapshot');
446
+ result.brownfieldSuggestions.push('如果是 brownfield 任务,在 delivery-plan.md 中补齐 Brownfield Context Snapshot;必要时引用 Graphify 或 GitNexus 图谱证据。');
447
447
  }
448
448
  addProjectContextRemediation(result, taskDir);
449
449
  return finalizeResult(result, catalog);
@@ -462,7 +462,7 @@ function analyzeTask(options) {
462
462
 
463
463
  const deliveryPlanText = readText(deliveryPlanPath).toLowerCase();
464
464
  if (brownfieldRepo && !deliveryPlanText.includes('brownfield context snapshot')) {
465
- result.brownfieldSuggestions.push('建议在 delivery-plan.md 中补齐 Brownfield Context Snapshot');
465
+ result.brownfieldSuggestions.push('建议在 delivery-plan.md 中补齐 Brownfield Context Snapshot;必要时引用 Graphify 或 GitNexus 图谱证据。');
466
466
  }
467
467
 
468
468
  if (!hasHandoffEvidence && !exists(executeLogPath)) {
@@ -0,0 +1,60 @@
1
+ ---
2
+ name: gitnexus
3
+ description: >
4
+ 将 GitNexus 作为受控可选代码智能能力接入,用于 brownfield MCP 查询、影响面分析、
5
+ detect_changes、多仓分析和更深代码图谱证据。输出必须回落到 `/team-*` 主链和 artifacts。
6
+ origin: abhigyanpatwari/GitNexus (reference-only controlled integration)
7
+ ---
8
+
9
+ # GitNexus
10
+
11
+ ## 用途
12
+
13
+ - 对存量仓库做更深代码图谱分析:符号上下游、调用链、执行流、跨仓影响面。
14
+ - 在改动前用 `impact` / `detect_changes` 类证据确认 blast radius。
15
+ - 为 `/team-plan`、`/team-execute`、`/team-review` 提供可追溯的 MCP/图谱证据。
16
+
17
+ ## 触发信号
18
+
19
+ - brownfield 项目改动跨多个模块、服务或仓库。
20
+ - 需要回答“改这个 symbol/API 会影响谁”“这段流程从哪里进入、流向哪里”。
21
+ - 评审或发布前需要对 git diff 做影响面确认。
22
+ - Graphify 的轻量结构扫描不够,需要 MCP tool、资源或多仓上下文。
23
+
24
+ ## 默认工作流
25
+
26
+ 1. 先跑 `npm run gitnexus:doctor`,确认 Node、npm/npx 与上游包元数据。
27
+ 2. 用户自行确认 GitNexus 上游许可证和项目使用场景是否匹配。
28
+ 3. 在目标项目根目录显式执行索引命令,并保留 TSP 的 AGENTS/CLAUDE 契约:
29
+ ```bash
30
+ npx --yes gitnexus@latest analyze --skip-agents-md
31
+ ```
32
+ 4. 通过 MCP 或 CLI 查询 `query/context/impact/detect_changes` 等结果。
33
+ 5. 把关键发现回落到主链:
34
+ - 规划阶段 -> `/team-plan` 的 Brownfield Context Snapshot 和 readiness 证据
35
+ - 执行阶段 -> `/team-execute` 的 story slice 影响面说明
36
+ - 评审阶段 -> `/team-review` 的风险、回归边界和放行建议
37
+
38
+ ## 输出约定
39
+
40
+ - GitNexus 索引由上游工具管理,通常写入目标仓库 `.gitnexus/` 与用户级 registry。
41
+ - TSP 侧只沉淀结论,不沉淀上游数据库:
42
+ - 分析目标
43
+ - 查询入口(MCP tool/resource 或 CLI 命令)
44
+ - 核心发现
45
+ - 对 `/team-*` 决策的影响
46
+ - 后续验证或回退建议
47
+
48
+ ## 边界与禁用项
49
+
50
+ - 不把 GitNexus 当作 TSP 依赖或默认安装项。
51
+ - 不自动运行 `gitnexus setup`,避免改写用户全局 MCP/editor 配置。
52
+ - 不在 TSP 管理仓库里运行不带 `--skip-agents-md` 的 `gitnexus analyze`。
53
+ - 不复制 GitNexus 源码、hooks、skills 或生成产物到 TSP canonical source。
54
+ - 当前按上游 npm 元数据视为非商业许可证约束;商业使用前需要用户自行确认授权。
55
+
56
+ ## 推荐组合
57
+
58
+ - 轻量 brownfield 结构扫描:`/team-help -> /update-codemaps -> graphify -> /team-plan`
59
+ - 深影响面分析:`/team-help -> /update-codemaps -> GitNexus impact/detect_changes -> /team-plan`
60
+ - 高风险评审:`/team-execute -> GitNexus detect_changes -> /handoff -> /team-review`
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "GitNexus Code Intelligence"
3
+ short_description: "用 GitNexus 补齐 brownfield MCP 查询与影响面证据"
4
+ default_prompt: "Use $gitnexus when brownfield work needs MCP-backed code intelligence, impact analysis, detect_changes, or multi-repo graph evidence. Keep GitNexus optional, preserve existing AGENTS/CLAUDE contracts, and feed findings back into /team-plan, /team-execute, or /team-review."