@thinkingai/ae-cli 6.0.53 → 6.0.55

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 (121) hide show
  1. package/README.md +28 -9
  2. package/README.zh.md +25 -9
  3. package/bin/ae-cli.js +6 -1
  4. package/cli-installation-guide.md +91 -0
  5. package/dist/{auth-LHM7NQPR.js → auth-RQBO6MTY.js} +9 -11
  6. package/dist/automation-VAAQBM7Y.js +459 -0
  7. package/dist/{capability-F2RIR5DS.js → capability-JRIFC4AJ.js} +6 -6
  8. package/dist/{chunk-647BRKZK.js → chunk-374V5R6N.js} +3 -3
  9. package/dist/{chunk-KJDOTPYU.js → chunk-3YVEAZUN.js} +59 -26
  10. package/dist/{chunk-KS4DE3MX.js → chunk-5XJYLEVC.js} +4 -4
  11. package/dist/{chunk-BW7JUDEI.js → chunk-6VNIR4AP.js} +514 -213
  12. package/dist/{chunk-5XRQ3CZR.js → chunk-7KU2WNQE.js} +43 -8
  13. package/dist/{chunk-7WZACOAI.js → chunk-AP7YP2NX.js} +2 -2
  14. package/dist/chunk-CI24BDR2.js +43 -0
  15. package/dist/{chunk-SO5LKIWP.js → chunk-CT7QDJS6.js} +2 -2
  16. package/dist/{chunk-DU72X2IO.js → chunk-GHXZK54S.js} +22 -10
  17. package/dist/{chunk-N4BLPJC7.js → chunk-OP44TT4H.js} +1 -1
  18. package/dist/chunk-Q5US7JC5.js +133 -0
  19. package/dist/{chunk-PGCIKZRE.js → chunk-RGYPSEYD.js} +3 -3
  20. package/dist/chunk-RK6GLDGI.js +27 -0
  21. package/dist/chunk-RPBOAQQQ.js +56 -0
  22. package/dist/{chunk-GK7WDY7E.js → chunk-S6M42KND.js} +1 -1
  23. package/dist/{chunk-QYXDJOLA.js → chunk-VZZNLV3I.js} +19 -11
  24. package/dist/{chunk-YVGXGTD6.js → chunk-WNGFSIOB.js} +7 -1
  25. package/dist/{chunk-IG3DYEIR.js → chunk-ZL7SGNIA.js} +5 -3
  26. package/dist/{community-report-client-6VJDDMJJ.js → community-report-client-PEERQCX2.js} +1 -1
  27. package/dist/{config-XQWOPDHN.js → config-SICKWZ2A.js} +8 -8
  28. package/dist/context-NGVFPCBK.js +136 -0
  29. package/dist/{data-integration-XBTCGXQT.js → data-integration-BCLQFTGU.js} +6 -6
  30. package/dist/index.js +125 -48
  31. package/dist/{local-data-upload-client-3P7NIVQ4.js → local-data-upload-client-TWMHFLUA.js} +1 -1
  32. package/dist/memory-CQKH2JAV.js +580 -0
  33. package/dist/{metadata-V5ZDTKWZ.js → metadata-EMFGUI2W.js} +7 -7
  34. package/dist/{model-ZS7HVGKN.js → model-VX5M2CJ6.js} +9 -5
  35. package/dist/{personal-semantic-preference-2XANU3F6.js → personal-semantic-preference-SLWVVYZ4.js} +117 -11
  36. package/dist/{project-semantic-LP3LY7QP.js → project-semantic-DPSZBKQD.js} +6 -6
  37. package/dist/{sync-EQLSTUS7.js → sync-RZIAV37C.js} +12 -8
  38. package/dist/{te-agent-Z3HCTKR7.js → te-agent-AORRE2FM.js} +90 -454
  39. package/dist/{te-analysis-3EVYQ3IO.js → te-analysis-ISXKJKRT.js} +297 -20
  40. package/dist/{te-community-Z7JACGF3.js → te-community-42M7ICDO.js} +3 -3
  41. package/dist/{te-dataops-F7GQOIS6.js → te-dataops-BNKNZ4F2.js} +5 -5
  42. package/dist/{te-engage-LFBU6WWH.js → te-engage-BZE3MBKT.js} +6 -6
  43. package/dist/{te-kb-QJLJEX5K.js → te-kb-ULATOTQ3.js} +494 -214
  44. package/dist/{te-system-IXC43QHZ.js → te-system-UGHVKBVV.js} +10 -9
  45. package/dist/{te-team-BIZIKIHZ.js → te-team-YA6SNOJ3.js} +6 -6
  46. package/dist/things-DTCOQIQB.js +1091 -0
  47. package/dist/{update-W2SGQNKT.js → update-ESNYKUVR.js} +59 -51
  48. package/dist/workspace-JEOHKFPX.js +554 -0
  49. package/package.json +21 -9
  50. package/skills/ae-agent/SKILL.md +91 -139
  51. package/skills/ae-agent/references/add-model.md +1 -1
  52. package/skills/ae-agent/references/create-agent.md +1 -1
  53. package/skills/ae-agent/references/find-archived-conversations.md +12 -5
  54. package/skills/ae-agent/references/get-agent.md +1 -1
  55. package/skills/ae-agent/references/list-agents.md +2 -2
  56. package/skills/ae-agent/references/list-models.md +2 -2
  57. package/skills/ae-agent/references/restore-conversation.md +3 -1
  58. package/skills/ae-analysis/SKILL.md +15 -6
  59. package/skills/ae-analysis/references/ai_models/funnel.md +5 -3
  60. package/skills/ae-analysis/references/ai_models.md +27 -0
  61. package/skills/ae-analysis/references/command_index.md +12 -4
  62. package/skills/ae-analysis/references/dashboard_move.md +21 -0
  63. package/skills/ae-analysis/references/dashboard_report_add.md +2 -0
  64. package/skills/ae-analysis/references/dashboard_report_get.md +19 -0
  65. package/skills/ae-analysis/references/dashboard_report_update.md +27 -0
  66. package/skills/ae-analysis/references/dashboard_update.md +1 -1
  67. package/skills/ae-analysis/references/event_report_presentation.md +76 -0
  68. package/skills/ae-analysis/references/feedback_submit.md +37 -0
  69. package/skills/ae-analysis/references/metadata_resolution.md +15 -0
  70. package/skills/ae-analysis/references/personal_semantic_preference_review.md +9 -0
  71. package/skills/ae-analysis/references/personal_semantic_preference_review_list.md +7 -0
  72. package/skills/ae-analysis/references/personal_semantic_preference_submit.md +7 -0
  73. package/skills/ae-analysis/references/project_semantic_knowledge_wiki.md +90 -31
  74. package/skills/ae-analysis/references/project_semantic_knowledge_wiki_plan_schema.md +99 -1
  75. package/skills/ae-analysis/references/report_create.md +13 -3
  76. package/skills/ae-analysis/references/report_get.md +5 -1
  77. package/skills/ae-analysis/references/report_update.md +15 -4
  78. package/skills/ae-analysis/references/system_query_monitor_overview.md +2 -0
  79. package/skills/ae-analysis/references/system_query_task_cancel.md +2 -0
  80. package/skills/ae-analysis/references/system_query_task_export.md +6 -2
  81. package/skills/ae-analysis/references/system_query_task_get.md +2 -0
  82. package/skills/ae-analysis/references/system_query_task_list.md +7 -3
  83. package/skills/ae-analysis/references/system_query_task_options.md +2 -2
  84. package/skills/ae-analysis/references/system_query_task_realtime_list.md +15 -0
  85. package/skills/ae-analysis/references/user_tag_models.md +2 -0
  86. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/build-empty-skeleton.mjs +105 -0
  87. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/build-project-semantic-wiki.mjs +669 -29
  88. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/company-kb.mjs +21 -8
  89. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/default-compile-rules.md +28 -10
  90. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/default-compile-schema.md +66 -0
  91. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/generate-build-ir.mjs +285 -24
  92. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/package-wiki-source-zip.mjs +262 -30
  93. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/plan-kb-source-sync.mjs +1 -1
  94. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/precompiled-source.mjs +46 -2
  95. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/prepare-compile-schema.mjs +64 -0
  96. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/refresh-state-parts.mjs +91 -0
  97. package/skills/ae-automation/SKILL.md +16 -0
  98. package/skills/ae-automation/references/cli.md +53 -0
  99. package/skills/ae-automation/references/command_index.md +17 -0
  100. package/skills/ae-capability/SKILL.md +15 -6
  101. package/skills/ae-current-context/SKILL.md +3 -2
  102. package/skills/ae-kb/SKILL.md +62 -12
  103. package/skills/ae-kb/references/analysis-workflow.md +73 -36
  104. package/skills/ae-kb/references/query-workflow.md +67 -33
  105. package/skills/ae-kb/references/schema-import.md +5 -3
  106. package/skills/ae-kb/references/source-mutations.md +6 -4
  107. package/skills/ae-kb/references/text-sources.md +53 -0
  108. package/skills/ae-kb-discovery/SKILL.md +1 -1
  109. package/skills/ae-team/SKILL.md +23 -17
  110. package/skills/ae-things/SKILL.md +85 -0
  111. package/skills/ae-things/references/cli.md +103 -0
  112. package/skills/ae-things-progress/SKILL.md +43 -0
  113. package/skills/ae-things-review/SKILL.md +42 -0
  114. package/skills/ae-workspace/SKILL.md +43 -0
  115. package/skills/ae-workspace/references/commands.md +51 -0
  116. package/dist/chunk-E2JKXMVO.js +0 -29
  117. package/dist/context-M3HDGEIR.js +0 -46
  118. package/dist/memory-6APEB2JB.js +0 -892
  119. package/skills/ae-agent/references/create-automation.md +0 -125
  120. package/skills/ae-agent/references/list-automations.md +0 -56
  121. package/skills/ae-agent/references/update-automation.md +0 -107
@@ -4,6 +4,7 @@ import { normalizeSourceForHash } from './precompiled-source.mjs';
4
4
  import path from 'node:path';
5
5
  import { createRequire } from 'node:module';
6
6
  import { createHash, randomBytes } from 'node:crypto';
7
+ import { decodeRefreshState, encodeRefreshState, readRefreshStateIndex } from './refresh-state-parts.mjs';
7
8
 
8
9
  const args = parseArgs(process.argv.slice(2));
9
10
  const sourceDir = path.resolve(required('source-dir'));
@@ -11,18 +12,27 @@ const sourceRoot = fs.realpathSync(sourceDir);
11
12
  const treeOutput = path.resolve(args['tree-output'] || `${sourceDir}-source-tree`);
12
13
  const outputArchive = args.output ? path.resolve(args.output) : '';
13
14
  const previousManifestPath = args['previous-manifest'] ? path.resolve(args['previous-manifest']) : '';
15
+ const previousRefreshStatePath = args['previous-refresh-state'] ? path.resolve(args['previous-refresh-state']) : '';
16
+ const previousTreeRoot = args['previous-tree-root'] ? fs.realpathSync(path.resolve(args['previous-tree-root'])) : '';
14
17
  const manifestOutput = path.resolve(args['manifest-output'] || path.join(treeOutput, 'kb-source-tree-manifest.json'));
15
18
  const force = Boolean(args.force);
19
+ const shouldEmbedRefreshStateBaseline = Boolean(args['embed-refresh-state-baseline']);
16
20
  const rootPath = normalizeRootPath(args['root-path'] || 'sources');
17
21
 
18
- if (!outputArchive && !previousManifestPath) throw new Error('--output or --previous-manifest is required.');
22
+ if (previousManifestPath && previousRefreshStatePath) throw new Error('Use either --previous-manifest or --previous-refresh-state, not both.');
23
+ if (!outputArchive && !previousManifestPath && !previousRefreshStatePath && !shouldEmbedRefreshStateBaseline) {
24
+ throw new Error('--output, --previous-manifest, --previous-refresh-state, or --embed-refresh-state-baseline is required.');
25
+ }
19
26
  assertOutputPath(treeOutput, sourceRoot, 'Tree output');
20
27
  if (outputArchive) assertOutputPath(outputArchive, sourceRoot, 'Archive');
21
28
  if (manifestOutput.startsWith(`${sourceRoot}${path.sep}`)) throw new Error('Manifest output must not be nested inside the flat source directory.');
22
29
 
23
30
  const flatManifest = readManifest(path.join(sourceRoot, 'kb-upload-manifest.json'), { allowFlat: true });
24
31
  const next = buildSourceTreeManifest(flatManifest, { requireLocalContent: true });
25
- const entries = buildSourceTreeEntries(next);
32
+ const nextBaseline = shouldEmbedRefreshStateBaseline ? refreshStateBaseline(next) : null;
33
+ const entries = buildSourceTreeEntries(next, nextBaseline);
34
+ const stateEntry = entries.find((entry) => entry.source_kind === 'refresh-state');
35
+ const stateParts = entries.filter((entry) => entry.source_kind === 'refresh-state-part');
26
36
 
27
37
  materializeTree(treeOutput, force, (staging) => {
28
38
  for (const entry of entries) writeText(path.join(staging, entry.path), entry.content);
@@ -34,15 +44,34 @@ const result = {
34
44
  namespace: next.namespace,
35
45
  root_path: next.root_path,
36
46
  source_count: next.source_count,
47
+ refresh_state_part_count: stateParts.length,
37
48
  tree_output: treeOutput,
38
49
  manifest: manifestOutput,
39
50
  };
40
51
 
41
- if (previousManifestPath) {
42
- const previous = buildSourceTreeManifest(readManifest(previousManifestPath, { allowFlat: true, allowTree: true }), { requireLocalContent: false });
52
+ if (previousManifestPath || previousRefreshStatePath) {
53
+ const previousState = previousManifestPath
54
+ ? { manifest: readManifest(previousManifestPath, { allowFlat: true, allowTree: true }), baseline: null, index: null }
55
+ : readRefreshStateBaseline(previousRefreshStatePath);
56
+ const previous = buildSourceTreeManifest(previousState.manifest, { requireLocalContent: false });
57
+ if (previousManifestPath && previousTreeRoot) {
58
+ const previousRefresh = previous.sources.find((source) => source.source_kind === 'refresh-state');
59
+ if (previousRefresh) {
60
+ const file = path.join(previousTreeRoot, previousRefresh.file);
61
+ if (fs.existsSync(file)) {
62
+ const content = readText(file);
63
+ previousState.index = readRefreshStateIndex(content);
64
+ previousState.index_hash = previousState.index ? sha256(content) : null;
65
+ }
66
+ }
67
+ }
68
+ if (previousTreeRoot) rehashPreviousTreeSources(previous, previousTreeRoot);
43
69
  if (previous.namespace !== next.namespace || previous.semantic_project_id !== next.semantic_project_id) {
44
70
  throw new Error('Wiki namespace/project changed; stop.');
45
71
  }
72
+ const semanticPlanDiff = diffSemanticPlanFragments(previous.semantic_plan_state, next.semantic_plan_state);
73
+ const taxonomyChanged = previous.navigation_taxonomy_version !== next.navigation_taxonomy_version;
74
+ const changedPlanFragments = new Set(semanticPlanDiff.changed_fragments);
46
75
  const before = new Map(previous.sources.map((source) => [source.file, source]));
47
76
  const after = new Map(next.sources.map((source) => [source.file, source]));
48
77
  const actions = [];
@@ -50,35 +79,72 @@ if (previousManifestPath) {
50
79
  for (const source of next.sources) {
51
80
  const old = before.get(source.file);
52
81
  if (old?.content_hash === source.content_hash) unchanged.push(source.file);
53
- else actions.push({
54
- action: old ? 'replace' : 'add',
55
- path: source.file,
56
- file: path.join(treeOutput, source.file),
57
- source_kind: source.source_kind,
58
- asset_type: source.asset_type,
59
- asset_key: source.asset_key,
60
- content_hash: source.content_hash,
61
- previous_content_hash: old?.content_hash ?? null,
62
- });
82
+ else actions.push(actionForSource(old ? 'replace' : 'add', source, old, changedPlanFragments));
63
83
  }
64
84
  for (const source of previous.sources) {
65
- if (!after.has(source.file)) actions.push({
66
- action: 'remove',
85
+ if (!after.has(source.file)) actions.push(actionForSource('remove', source, source, changedPlanFragments));
86
+ }
87
+ if (stateEntry) {
88
+ const oldIndex = previousState.index;
89
+ const oldParts = new Map((oldIndex?.parts ?? []).map((part) => [part.file, part]));
90
+ const nextIndex = readRefreshStateIndex(stateEntry.content);
91
+ const oldState = before.get(stateEntry.path);
92
+ if (oldState && oldState.content_hash === after.get(stateEntry.path)?.content_hash
93
+ && (!oldIndex || previousState.index_hash !== sha256(stateEntry.content))) {
94
+ actions.push(actionForSource('replace', after.get(stateEntry.path), oldState, changedPlanFragments));
95
+ }
96
+ for (const part of stateParts) {
97
+ const old = oldParts.get(path.posix.basename(part.path));
98
+ if (old?.sha256 !== sha256(part.content)) actions.push(statePartAction(old ? 'replace' : 'add', part, old));
99
+ oldParts.delete(path.posix.basename(part.path));
100
+ }
101
+ for (const old of oldParts.values()) {
102
+ actions.push(statePartAction('remove', { path: path.posix.join(path.posix.dirname(stateEntry.path), old.file) }, old));
103
+ }
104
+ if (!nextIndex) throw new Error('Packaged refresh state index is missing.');
105
+ const stateAction = actions.find((action) => action.path === stateEntry.path);
106
+ if (stateAction) {
107
+ const unchangedPosition = unchanged.indexOf(stateEntry.path);
108
+ if (unchangedPosition !== -1) unchanged.splice(unchangedPosition, 1);
109
+ stateAction.content_hash = sha256(stateEntry.content);
110
+ if (oldIndex) stateAction.previous_content_hash = previousState.index_hash;
111
+ }
112
+ const ordinaryActions = actions.filter((action) => !['refresh-state', 'refresh-state-part'].includes(action.source_kind));
113
+ const partWrites = actions.filter((action) => action.source_kind === 'refresh-state-part' && action.action !== 'remove');
114
+ const partRemovals = actions.filter((action) => action.source_kind === 'refresh-state-part' && action.action === 'remove');
115
+ actions.splice(0, actions.length, ...ordinaryActions, ...partWrites, ...(stateAction ? [stateAction] : []), ...partRemovals);
116
+ }
117
+ const affectedBySemanticPlan = next.sources
118
+ .map((source) => ({
67
119
  path: source.file,
68
120
  source_kind: source.source_kind,
69
121
  asset_type: source.asset_type,
70
122
  asset_key: source.asset_key,
71
- previous_content_hash: source.content_hash,
72
- });
73
- }
74
- Object.assign(result, { actions, unchanged, compile_mode: actions.length ? 'incremental' : 'skip' });
123
+ changed_semantic_plan_fragments: intersect(source.semantic_plan_fragment_refs, changedPlanFragments),
124
+ }))
125
+ .filter((source) => source.changed_semantic_plan_fragments.length);
126
+ Object.assign(result, {
127
+ actions,
128
+ unchanged,
129
+ compile_mode: taxonomyChanged ? 'full' : actions.some((action) => !['refresh-state', 'refresh-state-part'].includes(action.source_kind)) ? 'incremental' : 'skip',
130
+ navigation_taxonomy_migration: taxonomyChanged ? {
131
+ from: previous.navigation_taxonomy_version ?? 'legacy',
132
+ to: next.navigation_taxonomy_version,
133
+ requires_schema_force: false,
134
+ requires_schema_review: true,
135
+ reason: 'navigation_taxonomy_changed',
136
+ } : null,
137
+ semantic_plan_diff: semanticPlanDiff,
138
+ affected_by_semantic_plan: affectedBySemanticPlan,
139
+ previous_refresh_state_baseline: previousState.baseline,
140
+ });
75
141
  }
76
142
 
77
143
  if (outputArchive) {
78
144
  if (fs.existsSync(outputArchive) && !force) throw new Error('Archive already exists. Pass --force to replace it.');
79
- const archiveEntries = next.sources.map((source) => ({
80
- name: source.file,
81
- content: fs.readFileSync(path.join(treeOutput, source.file)),
145
+ const archiveEntries = entries.map((entry) => ({
146
+ name: entry.path,
147
+ content: fs.readFileSync(path.join(treeOutput, entry.path)),
82
148
  })).sort((a, b) => a.name.localeCompare(b.name, 'en'));
83
149
  await writeZip(outputArchive, archiveEntries);
84
150
  Object.assign(result, { archive: outputArchive, archive_bytes: fs.statSync(outputArchive).size });
@@ -107,6 +173,7 @@ function buildSourceTreeManifest(manifest, options) {
107
173
  title: source.title,
108
174
  content_hash: content == null ? source.content_hash : sha256(normalizeContentForHash(content)),
109
175
  source_path: source.source_path,
176
+ semantic_plan_fragment_refs: source.semantic_plan_fragment_refs,
110
177
  managed_by: 'project-semantic-kb-source-tree',
111
178
  };
112
179
  });
@@ -128,6 +195,7 @@ function buildSourceTreeManifest(manifest, options) {
128
195
  return {
129
196
  schema_version: '1.1',
130
197
  manifest_kind: 'ae_project_semantic_kb_source_tree',
198
+ navigation_taxonomy_version: manifest.navigation_taxonomy_version ?? 'legacy',
131
199
  namespace: manifest.namespace,
132
200
  semantic_project_id: manifest.semantic_project_id,
133
201
  project_name: manifest.project_name ?? null,
@@ -137,32 +205,87 @@ function buildSourceTreeManifest(manifest, options) {
137
205
  source_count: sources.length,
138
206
  generated_source_count: generated.length,
139
207
  filing_guide_count: readmes.length,
208
+ semantic_plan_state: normalizeSemanticPlanState(manifest.semantic_plan_state),
140
209
  sources,
141
210
  };
142
211
  }
143
212
 
144
- function buildSourceTreeEntries(manifest) {
213
+ function buildSourceTreeEntries(manifest, refreshStateBaseline = null) {
145
214
  const flatToTree = new Map(manifest.sources
146
215
  .filter((item) => item.source_kind !== 'filing-guide')
147
216
  .map((item) => [item.display_name, item.file]));
148
217
  const guides = new Map(sourceDirectoryReadmes(manifest).map((entry) => [entry.path, entry.content]));
149
- return manifest.sources.map((source) => {
218
+ return manifest.sources.flatMap((source) => {
150
219
  if (source.source_kind === 'filing-guide') {
151
220
  const content = guides.get(source.file);
152
221
  if (!content) throw new Error(`Missing filing guide content for ${source.file}.`);
153
- return { path: source.file, content };
222
+ return [{ path: source.file, content, source_kind: source.source_kind }];
154
223
  }
155
- return {
224
+ if (source.source_kind === 'refresh-state') {
225
+ const content = rewriteLinksForTree(readText(path.join(sourceRoot, source.display_name)), source.display_name, flatToTree);
226
+ const state = encodeRefreshState(content, source.file, source.content_hash, refreshStateBaseline);
227
+ return [{ path: source.file, content: state.indexContent, source_kind: 'refresh-state' },
228
+ ...state.parts.map((part) => ({ ...part, source_kind: 'refresh-state-part' }))];
229
+ }
230
+ return [{
156
231
  path: source.file,
157
232
  content: rewriteLinksForTree(readText(path.join(sourceRoot, source.display_name)), source.display_name, flatToTree),
158
- };
233
+ source_kind: source.source_kind,
234
+ }];
159
235
  });
160
236
  }
161
237
 
238
+ function refreshStateBaseline(manifest) {
239
+ const contentRevision = optionalPositiveInteger(args['baseline-content-revision'], 'baseline-content-revision');
240
+ return {
241
+ schema_version: '1.0',
242
+ source_id: clean(args['baseline-source-id']) || null,
243
+ source_display_name: clean(args['baseline-source-display-name']) || null,
244
+ content_revision: contentRevision,
245
+ published_version_id: clean(args['baseline-published-version-id']) || null,
246
+ manifest_hash: sourceTreeManifestHash(manifest),
247
+ source_tree_manifest: manifest,
248
+ };
249
+ }
250
+
251
+ function readRefreshStateBaseline(file) {
252
+ const content = readText(file);
253
+ const { payload, index } = decodeRefreshState(content, file);
254
+ const baseline = payload.source_tree_baseline ?? null;
255
+ const manifest = baseline?.source_tree_manifest;
256
+ if (!manifest) throw new Error('Refresh state does not contain source_tree_baseline.source_tree_manifest.');
257
+ const expectedHash = clean(baseline.manifest_hash);
258
+ const actualHash = sourceTreeManifestHash(manifest);
259
+ if (expectedHash && expectedHash !== actualHash) {
260
+ throw new Error('Refresh state source_tree_manifest hash does not match its manifest_hash.');
261
+ }
262
+ const stateSource = manifest.sources.find((source) => source.source_kind === 'refresh-state');
263
+ if (index && stateSource?.content_hash !== index.normalized_content_hash) {
264
+ throw new Error('Refresh state normalized content hash does not match its source tree manifest.');
265
+ }
266
+ return {
267
+ manifest,
268
+ index,
269
+ index_hash: index ? sha256(content) : null,
270
+ baseline: {
271
+ schema_version: clean(baseline.schema_version) || null,
272
+ source_id: clean(baseline.source_id) || null,
273
+ source_display_name: clean(baseline.source_display_name) || null,
274
+ content_revision: Number.isSafeInteger(baseline.content_revision) ? baseline.content_revision : null,
275
+ published_version_id: clean(baseline.published_version_id) || null,
276
+ manifest_hash: expectedHash || actualHash,
277
+ },
278
+ };
279
+ }
280
+
281
+ function sourceTreeManifestHash(manifest) {
282
+ return sha256(stableJson(manifest));
283
+ }
284
+
162
285
  function sourceDirectoryReadmes(manifest) {
163
286
  const project = clean(manifest.project_name) || `Project ${manifest.semantic_project_id}`;
164
287
  const lines = [
165
- [`${rootPath}/README.md`, `# Source Directory\n\nThis directory is the writable source space for ${project} project knowledge. README files are filing guides only; they are not business evidence.\n\nPut source material by nature. CLI-managed asset facts live under assets/. Human context such as project background, meetings, decisions, corrections, and references must not be removed by an asset refresh.\n\nThe expected compiled Wiki should be organized for consumption: index.md, wiki/project/, wiki/domains/, wiki/semantics/, wiki/assets/, wiki/decisions/, and wiki/review/.\n`],
288
+ [`${rootPath}/README.md`, `# Source Directory\n\nThis directory is the writable source space for ${project} project knowledge. README files are filing guides only; they are not business evidence.\n\nPut source material by nature. CLI-managed asset facts live under assets/. Human context such as project background, meetings, decisions, corrections, and references must not be removed by an asset refresh.\n\nThe compiled Wiki navigation follows project overview, shared project knowledge, business domains, event and asset catalog, and governance with provenance. Event groups are lookup indexes, not business domains.\n`],
166
289
  [`${rootPath}/project/README.md`, '# Project Sources\n\nBackground, goals, business scope, terminology, and ownership notes. This guide is not evidence.\n'],
167
290
  [`${rootPath}/assets/README.md`, '# Asset Sources\n\nCLI-managed structured asset facts. Refresh may change files owned by the project semantic asset package only.\n'],
168
291
  [`${rootPath}/assets/dashboards/README.md`, '# Dashboard Asset Sources\n\nDashboard facts exported by CLI.\n'],
@@ -170,6 +293,11 @@ function sourceDirectoryReadmes(manifest) {
170
293
  [`${rootPath}/assets/metrics/README.md`, '# Metric Asset Sources\n\nReusable metric facts exported by CLI.\n'],
171
294
  [`${rootPath}/assets/metadata/README.md`, '# Metadata Asset Sources\n\nEvents, properties, tags, cohorts, and other selectable metadata facts exported by CLI.\n'],
172
295
  [`${rootPath}/business/README.md`, '# Business Sources\n\nBusiness rules, metric definitions, domain context, process notes, and user-facing question patterns.\n'],
296
+ [`${rootPath}/personal-semantics/README.md`, '# Adopted Personal Semantic Sources\n\nReviewed personal semantics promoted into project knowledge. Refresh may replace these CLI-managed files from adopted personal semantic records.\n'],
297
+ [`${rootPath}/personal-semantics/background/README.md`, '# Background Personal Semantics\n\nAdopted personal background context for project and domain interpretation.\n'],
298
+ [`${rootPath}/personal-semantics/preference/README.md`, '# Preference Personal Semantics\n\nAdopted personal preferences for routing, answer style, and source selection.\n'],
299
+ [`${rootPath}/personal-semantics/experience/README.md`, '# Experience Personal Semantics\n\nAdopted personal analysis experience, heuristics, and cautions.\n'],
300
+ [`${rootPath}/personal-semantics/asset-context/README.md`, '# Asset Context Personal Semantics\n\nAdopted personal context scoped to referenced project assets.\n'],
173
301
  [`${rootPath}/meetings/README.md`, '# Meeting Sources\n\nMeeting notes and discussion records. They can provide context, but do not override confirmed decisions by recency alone.\n'],
174
302
  [`${rootPath}/decisions/README.md`, '# Decision Sources\n\nConfirmed decisions must state confirmer, status, date, and applicable scope.\n'],
175
303
  [`${rootPath}/corrections/README.md`, '# Correction Sources\n\nCorrections must identify the target material, corrected content, reason, date, and applicable scope.\n'],
@@ -186,13 +314,27 @@ function sourceTreePath(source) {
186
314
  if (assetType === 'report') return `${rootPath}/assets/reports/${base}`;
187
315
  if (assetType === 'metric') return `${rootPath}/assets/metrics/${base}`;
188
316
  if (assetType === 'metadata' || kind === 'metadata') return `${rootPath}/assets/metadata/${base}`;
317
+ if (source.source_path.startsWith('wiki/event-map/')) return `${rootPath}/assets/metadata/${base}`;
318
+ if (source.source_path === 'wiki/project-overview.md') return `${rootPath}/project/${base}`;
319
+ if (source.source_path.startsWith('wiki/common/') || source.source_path.startsWith('wiki/asset-index/')) return `${rootPath}/project/${base}`;
320
+ if (kind === 'refresh-state') return `${rootPath}/project/${base}`;
189
321
  if (kind === 'domain' || kind === 'recall-card') return `${rootPath}/business/${base}`;
322
+ if (kind === 'personal-semantic') return personalSemanticSourceTreePath(source, base);
190
323
  if (kind === 'index' || kind === 'manifest') return `${rootPath}/project/${base}`;
191
324
  if (kind === 'governance') return `${rootPath}/references/governance/${base}`;
192
325
  if (kind === 'appendix' || kind === 'source-container') return `${rootPath}/references/${base}`;
193
326
  return `${rootPath}/references/${base}`;
194
327
  }
195
328
 
329
+ function personalSemanticSourceTreePath(source, base) {
330
+ const match = clean(source.source_path).match(/^wiki\/personal-semantics(?:\/([^/]+))?\//);
331
+ const folder = match?.[1];
332
+ if (['background', 'preference', 'experience', 'asset-context'].includes(folder)) {
333
+ return `${rootPath}/personal-semantics/${folder}/${base}`;
334
+ }
335
+ return `${rootPath}/personal-semantics/${base}`;
336
+ }
337
+
196
338
  function sourceTreeBaseName(source) {
197
339
  const original = path.posix.basename(source.file);
198
340
  if (!source.asset_key) return original;
@@ -259,6 +401,7 @@ function normalizeFlatSource(manifest, source, requireLocalFile) {
259
401
  asset_key: clean(source.asset_key) || null,
260
402
  title: clean(source.title) || clean(source.display_name).replace(/\.md$/i, ''),
261
403
  source_path: clean(source.source_path),
404
+ semantic_plan_fragment_refs: normalizeStringArray(source.semantic_plan_fragment_refs),
262
405
  };
263
406
  }
264
407
 
@@ -273,20 +416,96 @@ function validateTreeManifest(manifest) {
273
416
  || !/^[a-f0-9]{64}$/i.test(source.content_hash) || seen.has(source.file)) {
274
417
  throw new Error('Invalid or duplicate source tree path/hash.');
275
418
  }
419
+ source.semantic_plan_fragment_refs = normalizeStringArray(source.semantic_plan_fragment_refs);
276
420
  seen.add(source.file);
277
421
  }
422
+ manifest.semantic_plan_state = normalizeSemanticPlanState(manifest.semantic_plan_state);
278
423
  return manifest;
279
424
  }
280
425
 
426
+ function actionForSource(action, source, previous, changedPlanFragments) {
427
+ return {
428
+ action,
429
+ path: source.file,
430
+ ...(action === 'remove' ? {} : { file: path.join(treeOutput, source.file) }),
431
+ source_kind: source.source_kind,
432
+ asset_type: source.asset_type,
433
+ asset_key: source.asset_key,
434
+ content_hash: action === 'remove' ? null : source.content_hash,
435
+ previous_content_hash: previous?.content_hash ?? null,
436
+ semantic_plan_fragment_refs: source.semantic_plan_fragment_refs ?? [],
437
+ changed_semantic_plan_fragments: intersect(source.semantic_plan_fragment_refs, changedPlanFragments),
438
+ };
439
+ }
440
+
441
+ function diffSemanticPlanFragments(previousState, nextState) {
442
+ const previous = new Map(normalizeSemanticPlanState(previousState).fragments.map((fragment) => [fragment.id, fragment.hash]));
443
+ const next = new Map(normalizeSemanticPlanState(nextState).fragments.map((fragment) => [fragment.id, fragment.hash]));
444
+ const ids = [...new Set([...previous.keys(), ...next.keys()])].sort((left, right) => left.localeCompare(right, 'en'));
445
+ const changed = ids.filter((id) => previous.get(id) !== next.get(id));
446
+ return {
447
+ changed_fragment_count: changed.length,
448
+ changed_fragments: changed,
449
+ };
450
+ }
451
+
452
+ function normalizeSemanticPlanState(value) {
453
+ if (!value || typeof value !== 'object') return { schema_version: null, plan_hash: null, fragment_count: 0, fragments: [] };
454
+ const fragments = Array.isArray(value.fragments) ? value.fragments
455
+ .map((fragment) => ({
456
+ ...fragment,
457
+ id: clean(fragment?.id),
458
+ kind: clean(fragment?.kind),
459
+ hash: clean(fragment?.hash),
460
+ }))
461
+ .filter((fragment) => fragment.id && /^[a-f0-9]{64}$/i.test(fragment.hash))
462
+ .sort((left, right) => left.id.localeCompare(right.id, 'en')) : [];
463
+ return {
464
+ ...value,
465
+ fragment_count: fragments.length,
466
+ fragments,
467
+ };
468
+ }
469
+
470
+ function normalizeStringArray(value) {
471
+ return [...new Set((Array.isArray(value) ? value : []).map(clean).filter(Boolean))].sort((left, right) => left.localeCompare(right, 'en'));
472
+ }
473
+
474
+ function intersect(values, set) {
475
+ const source = normalizeStringArray(values);
476
+ return source.filter((value) => set.has(value));
477
+ }
478
+
281
479
  function normalizeContentForHash(content) { return normalizeSourceForHash(String(content).replace(/\r\n/g, '\n')); }
282
480
 
481
+ function rehashPreviousTreeSources(manifest, root) {
482
+ for (const source of manifest.sources) {
483
+ const file = path.resolve(root, source.file);
484
+ if (!file.startsWith(`${root}${path.sep}`) || !fs.existsSync(file)) continue;
485
+ const content = readText(file);
486
+ source.content_hash = source.source_kind === 'refresh-state' && readRefreshStateIndex(content)
487
+ ? readRefreshStateIndex(content).normalized_content_hash
488
+ : sha256(normalizeContentForHash(content));
489
+ }
490
+ }
491
+
492
+ function statePartAction(action, part, previous) {
493
+ return {
494
+ action, path: part.path, ...(action === 'remove' ? {} : { file: path.join(treeOutput, part.path) }),
495
+ source_kind: 'refresh-state-part', asset_type: null, asset_key: null,
496
+ content_hash: action === 'remove' ? null : sha256(part.content),
497
+ previous_content_hash: previous?.sha256 ?? null,
498
+ semantic_plan_fragment_refs: [], changed_semantic_plan_fragments: [],
499
+ };
500
+ }
501
+
283
502
  function parseArgs(tokens) {
284
503
  const parsed = {};
285
504
  for (let i = 0; i < tokens.length; i += 1) {
286
505
  const key = tokens[i];
287
506
  if (!key.startsWith('--')) throw new Error(`Invalid argument: ${key}`);
288
507
  const name = key.slice(2);
289
- if (name === 'force') {
508
+ if (name === 'force' || name === 'embed-refresh-state-baseline') {
290
509
  parsed[name] = true;
291
510
  continue;
292
511
  }
@@ -303,6 +522,13 @@ function required(name) {
303
522
  return value;
304
523
  }
305
524
 
525
+ function optionalPositiveInteger(value, label) {
526
+ if (value == null || value === '') return null;
527
+ const parsed = Number(value);
528
+ if (!Number.isSafeInteger(parsed) || parsed < 1) throw new Error(`--${label} must be a positive integer.`);
529
+ return parsed;
530
+ }
531
+
306
532
  function normalizeRootPath(value) {
307
533
  const normalized = clean(value).replace(/^\/+|\/+$/g, '');
308
534
  if (!normalized || normalized.includes('\\') || normalized.split('/').some((part) => !part || part === '.' || part === '..')) {
@@ -374,3 +600,9 @@ function writeJson(file, value, replace) {
374
600
  function readText(file) { return fs.readFileSync(file, 'utf8'); }
375
601
  function clean(value) { return value == null ? '' : String(value).trim(); }
376
602
  function sha256(value) { return createHash('sha256').update(value).digest('hex'); }
603
+ function stableJson(value) {
604
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(',')}]`;
605
+ if (!value || typeof value !== 'object') return JSON.stringify(value);
606
+ const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right, 'en'));
607
+ return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(',')}}`;
608
+ }
@@ -212,7 +212,7 @@ function decideCompileMode({ actionCount, baselineCount, nextCount, fullThreshol
212
212
  mode: 'full',
213
213
  reason: forceReasons[0],
214
214
  changeRatio,
215
- requiresSchemaForce: true,
215
+ requiresSchemaForce: false,
216
216
  schemaForceReasons: forceReasons,
217
217
  };
218
218
  }
@@ -29,7 +29,14 @@ function renderFacts(value, depth = 0) {
29
29
 
30
30
  export function normalizeSourceForHash(content) {
31
31
  const volatileTimeKeys = '(?:created_at|updated_at|modified_at|deleted_at|generated_at|authenticated_at|create_time|update_time|modified_time|created_time|last_modified_at|last_modified_time|gmt_create|gmt_modified|ctime|mtime|snapshot_date|createdAt|updatedAt|modifiedAt|deletedAt|generatedAt|authenticatedAt|createTime|updateTime|modifiedTime|createdTime|lastModifiedAt|lastModifiedTime)';
32
- return content
32
+ return normalizeBulletSectionOrder(
33
+ normalizeBulletSectionOrder(
34
+ normalizeSemanticConflictCandidateOrder(normalizeRefreshStatePayload(content)),
35
+ '语义冲突',
36
+ ),
37
+ '缺少源标题的元数据',
38
+ )
39
+ .replace(/^\s*[-*]\s+semantic_plan_hash:\s*[^\r\n]*\r?\n?/gmi, '')
33
40
  .replace(/source_snapshot_hash:\s*[^\r\n]+/g, 'source_snapshot_hash: <snapshot>')
34
41
  .replace(/snapshot_hash:\s*[^\r\n]+/g, 'snapshot_hash: <snapshot>')
35
42
  .replace(/semantic_plan_hash:\s*[^\r\n]+/g, 'semantic_plan_hash: <semantic_plan>')
@@ -37,13 +44,50 @@ export function normalizeSourceForHash(content) {
37
44
  .replace(/"source_snapshot_hash":\s*"[^"]+"/g, '"source_snapshot_hash":"<snapshot>"')
38
45
  .replace(/"snapshot_hash":\s*"[^"]+"/g, '"snapshot_hash":"<snapshot>"')
39
46
  .replace(/"snapshot_id":\s*"[^"]+"/g, '"snapshot_id":"<snapshot_id>"')
40
- .replace(/"semantic_plan_hash":\s*"[^"]+"/g, '"semantic_plan_hash":"<semantic_plan>"')
47
+ .replace(/,?\s*"semantic_plan_hash":\s*"[^"]+"/g, '')
41
48
  .replace(new RegExp(`"(${volatileTimeKeys})":\\s*(?:"[^"]*"|null|-?\\d+(?:\\.\\d+)?)`, 'gi'), '"$1":"<volatile_time>"')
42
49
  .replace(/((?:创建|更新|修改|生成)时间):\s*[^\r\n]+/g, '$1: <volatile_time>')
43
50
  .replace(/((?:Created|Updated|Modified|Generated) at):\s*[^\r\n]+/gi, '$1: <volatile_time>')
51
+ .replace(/快照:\s*`[0-9a-f]{64}`/gi, '快照: `<snapshot>`')
44
52
  .replace(/Snapshot hash:\s*`[0-9a-f]{64}`/gi, 'Snapshot hash: `<snapshot>`')
45
53
  .replace(/Semantic plan hash:\s*`[0-9a-f]{64}`/gi, 'Semantic plan hash: `<semantic_plan>`')
46
54
  .replace(/## Snapshot [0-9a-f]{64}/gi, '## Snapshot <snapshot>')
47
55
  .replace(new RegExp(`^(\\s*-\\s+${volatileTimeKeys}):[^\\n]*`, 'gmi'), '$1: <volatile_time>')
48
56
  .replace(/^content_hash:[^\n]*$/gm, 'content_hash: <hash>');
49
57
  }
58
+
59
+ function normalizeRefreshStatePayload(content) {
60
+ return String(content).replace(/<!-- pskb_refresh_state_base64\n([A-Za-z0-9_=\-\s]+)\n-->/g, (match, encoded) => {
61
+ try {
62
+ const payload = JSON.parse(Buffer.from(encoded.replace(/\s+/g, ''), 'base64url').toString('utf8'));
63
+ delete payload.source_tree_baseline;
64
+ return `<!-- pskb_refresh_state_json\n${stableJson(payload)}\n-->`;
65
+ } catch {
66
+ return match;
67
+ }
68
+ });
69
+ }
70
+
71
+ function stableJson(value) {
72
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(',')}]`;
73
+ if (!value || typeof value !== 'object') return JSON.stringify(value);
74
+ const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right, 'en'));
75
+ return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(',')}}`;
76
+ }
77
+
78
+ function normalizeSemanticConflictCandidateOrder(content) {
79
+ return content.replace(/(语义“[^”]+”存在 \d+ 个[^,\r\n]+候选,但口径不同:)([^\r\n]*?)(。Agent 不应仅凭名称选择,需要优先使用业务域\/召回卡或让用户确认。)/g,
80
+ (_, prefix, candidates, suffix) => `${prefix}${candidates.split(';').sort((left, right) => left.localeCompare(right, 'zh-Hans-CN')).join(';')}${suffix}`);
81
+ }
82
+
83
+ function normalizeBulletSectionOrder(content, heading) {
84
+ return content.replace(new RegExp(`(## ${escapeRegExp(heading)}\\r?\\n\\r?\\n)([\\s\\S]*?)(?=\\r?\\n## |$)`, 'g'), (match, prefix, body) => {
85
+ const lines = body.split(/\r?\n/).filter((line) => line.trim());
86
+ if (!lines.length || !lines.every((line) => line.startsWith('- '))) return match;
87
+ return `${prefix}${lines.sort((left, right) => left.localeCompare(right, 'zh-Hans-CN')).join('\n')}\n`;
88
+ });
89
+ }
90
+
91
+ function escapeRegExp(value) {
92
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
93
+ }
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { createHash } from 'node:crypto';
5
+ import { pathToFileURL } from 'node:url';
6
+ import { runCompanyKb, invokeCompanyKbCli } from './company-kb.mjs';
7
+
8
+ const here = import.meta.dirname;
9
+ const missingCodes = new Set(['SCHEMA_NOT_FOUND', 'KB_SCHEMA_IMPORT_SCHEMA_NOT_FOUND']);
10
+
11
+ // Preparation never generates, validates with AI, applies rules, or compiles sources.
12
+ export async function prepareCompileSchema({ name, host, output, file, requestId }, invoke) {
13
+ if (!name || !host || !output) throw new Error('--name, --host and --output are required.');
14
+ if (fs.existsSync(output)) throw new Error('Output already exists; choose a new path.');
15
+ const call = (command, flags = []) => runCompanyKb(command, ['--name', name, '--host', host, ...flags], invoke);
16
+ const status = (await call('+status')).data;
17
+ if (status.activeRunId || status.schemaStatus === 'generating' || status.buildStatus === 'compiling'
18
+ || ['schema_generating', 'wiki_compiling', 'wiki_recovering', 'wiki_review_required'].includes(status.status)
19
+ || status.publicationReview) {
20
+ throw new Error(`Knowledge base is busy or requires publication review: ${status.status}`);
21
+ }
22
+ let absent = false;
23
+ try {
24
+ await call('+schema-download', ['--output', output]);
25
+ } catch (error) {
26
+ if (!missingCodes.has(error.code)) throw error;
27
+ absent = true;
28
+ }
29
+ if (!absent && status.schemaValidation?.valid === true) {
30
+ const bytes = fs.readFileSync(output);
31
+ const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
32
+ if (bytes.length > 262144 || [...text.trim()].length < 20) throw new Error('Downloaded formal rules failed basic checks.');
33
+ return { schema_action: 'reuse', compile_ready: true, schema_file: path.resolve(output),
34
+ schema_hash: createHash('sha256').update(bytes).digest('hex') };
35
+ }
36
+ // New KBs contain a placeholder file even though no effective rules exist yet.
37
+ const uninitialized = status.schemaStatus === 'pending' && ['empty', 'schema_pending'].includes(status.status)
38
+ && (!status.schemaValidation || status.schemaValidation.reason === 'DEFAULT_TEMPLATE');
39
+ if (!uninitialized) throw new Error('Formal rules are invalid or their state is unknown; review explicitly before initialization.');
40
+ if (!requestId) throw new Error('--request-id is required to initialize rules; reuse it when resuming the same upload.');
41
+ const candidate = (await call('+schema-import', ['--file', file || path.join(here, 'default-compile-schema.md'),
42
+ '--request-id', requestId])).data;
43
+ return { schema_action: 'review_import', compile_ready: false, candidate,
44
+ ...(absent ? {} : { schema_file: path.resolve(output) }) };
45
+ }
46
+
47
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
48
+ try {
49
+ const args = process.argv.slice(2), values = {};
50
+ const allowed = new Set(['name', 'host', 'output', 'file', 'request-id']);
51
+ for (let i = 0; i < args.length; i += 2) {
52
+ const key = args[i].replace(/^--/, '');
53
+ if (!args[i].startsWith('--') || !allowed.has(key) || !args[i + 1] || args[i + 1].startsWith('--')) {
54
+ throw new Error(`Invalid argument: ${args[i]}`);
55
+ }
56
+ values[key] = args[i + 1];
57
+ }
58
+ const result = await prepareCompileSchema({ ...values, requestId: values['request-id'] }, invokeCompanyKbCli);
59
+ console.log(JSON.stringify(result, null, 2));
60
+ } catch (error) {
61
+ console.error(error.message);
62
+ process.exitCode = 1;
63
+ }
64
+ }