diff --git a/scripts/kaifa-progress-sync.mjs b/scripts/kaifa-progress-sync.mjs index 9298242a..17fed1f5 100644 --- a/scripts/kaifa-progress-sync.mjs +++ b/scripts/kaifa-progress-sync.mjs @@ -195,6 +195,38 @@ export function validateStatusEvidence(status, options) { } } +export function buildInventoryCheck(progressSyncContext, reviewedAt = new Date().toISOString()) { + const currentState = progressSyncContext?.currentState + const modules = currentState?.modules + if (!Array.isArray(modules)) fail('INVENTORY_CONTEXT_MISSING_MODULES') + const items = modules.flatMap((module) => { + if (!module?.moduleId || !module?.name || !Array.isArray(module.features)) fail('INVENTORY_CONTEXT_INVALID_MODULE') + return module.features.map((feature) => { + if (!feature?.featureId || !feature?.name || !feature?.description || !feature?.status || !Number.isFinite(feature?.progressPercent)) { + fail(`INVENTORY_CONTEXT_INVALID_FEATURE:${module.name}/${feature?.name || 'unknown'}`) + } + return { + module_id: module.moduleId, + module_name: module.name, + feature_id: feature.featureId, + feature_name: feature.name, + feature_description: feature.description, + status: feature.status, + progress_percent: feature.progressPercent, + } + }) + }) + const catalogFeatureCount = currentState?.featureInventoryPolicy?.catalogFeatureCount + if (!Number.isInteger(catalogFeatureCount) || catalogFeatureCount !== items.length) fail('INVENTORY_CONTEXT_COUNT_MISMATCH') + return { + reviewed_at: reviewedAt, + source_inventory_complete: true, + source_feature_count: items.length, + catalog_feature_count: catalogFeatureCount, + items, + } +} + function git(repoRoot, args) { const result = spawnSync('git', args, { cwd: repoRoot, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }) if (result.status !== 0) fail(`GIT_FAILED:${args[0]}:${result.stderr.trim()}`) @@ -464,6 +496,7 @@ export async function main(argv = process.argv.slice(2)) { body: JSON.stringify({ context: apiContext, project_description: env.PROJECT_DESCRIPTION, + inventory_check: buildInventoryCheck(await apiRequest(apiBase, env.API_KEY, `/projects/${project.projectId}/progress-sync`)), commits: [commit], updates: [update], }), diff --git a/tests/scripts/kaifa-progress-sync.test.mjs b/tests/scripts/kaifa-progress-sync.test.mjs index fde6b35e..287593ed 100644 --- a/tests/scripts/kaifa-progress-sync.test.mjs +++ b/tests/scripts/kaifa-progress-sync.test.mjs @@ -3,6 +3,7 @@ import test from 'node:test' import { buildApiContext, + buildInventoryCheck, classifyCommit, makeFeatureCatalogKey, makeIdempotencyKey, @@ -11,6 +12,41 @@ import { validateStatusEvidence, } from '../../scripts/kaifa-progress-sync.mjs' +test('builds the complete inventory check from the freshly fetched catalog state', () => { + const context = { + currentState: { + featureInventoryPolicy: { catalogFeatureCount: 1 }, + modules: [{ + moduleId: 'module-1', + name: '生产发布与团队协作', + features: [{ + featureId: 'feature-1', + name: '钉钉账号登录与绑定', + description: '让现有财务账号通过钉钉完成绑定和登录。', + status: 'done', + progressPercent: 100, + }], + }], + }, + } + assert.deepEqual(buildInventoryCheck(context, '2026-09-03T10:00:00.000Z'), { + reviewed_at: '2026-09-03T10:00:00.000Z', + source_inventory_complete: true, + source_feature_count: 1, + catalog_feature_count: 1, + items: [{ + module_id: 'module-1', + module_name: '生产发布与团队协作', + feature_id: 'feature-1', + feature_name: '钉钉账号登录与绑定', + feature_description: '让现有财务账号通过钉钉完成绑定和登录。', + status: 'done', + progress_percent: 100, + }], + }) + assert.throws(() => buildInventoryCheck({ currentState: { featureInventoryPolicy: { catalogFeatureCount: 1 }, modules: [] } }), /COUNT_MISMATCH/) +}) + test('keeps Git author filtering separate from the board developer identity', () => { assert.deepEqual(buildApiContext({ OWNER_EMAIL: '549727955@qq.com',