@thinkingai/ae-cli 6.0.52 → 6.0.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -10
- package/README.zh.md +32 -10
- package/bin/ae-cli.js +6 -1
- package/cli-installation-guide.md +91 -0
- package/dist/{auth-XZNXFRJS.js → auth-MBWRZTB6.js} +171 -13
- package/dist/automation-NWWVLWG2.js +459 -0
- package/dist/{capability-RJZ4KRGP.js → capability-7SSN6PNZ.js} +11 -6
- package/dist/chunk-4655PUNX.js +43 -0
- package/dist/{chunk-4NRCTUZJ.js → chunk-5XJYLEVC.js} +4 -4
- package/dist/{chunk-JOCDD4ON.js → chunk-7QXISROD.js} +1 -1
- package/dist/{chunk-EQ255HKO.js → chunk-AP7YP2NX.js} +2 -2
- package/dist/{chunk-DVMLWQD4.js → chunk-CT7QDJS6.js} +2 -2
- package/dist/chunk-D6ACFILC.js +56 -0
- package/dist/{chunk-HFGZGYCG.js → chunk-EOYSKMXN.js} +264 -10
- package/dist/{chunk-57RRFUEZ.js → chunk-FMNNGNYO.js} +43 -8
- package/dist/{chunk-7XGFSUOM.js → chunk-GIK6EDUG.js} +291 -84
- package/dist/{chunk-WEPZ5M3Z.js → chunk-HBLK3ZHW.js} +7 -4
- package/dist/chunk-HVHR4SCP.js +133 -0
- package/dist/{chunk-DX5CDO34.js → chunk-KFNMQTZI.js} +580 -214
- package/dist/{chunk-HBGADCKA.js → chunk-PLE722AJ.js} +28 -11
- package/dist/chunk-RK6GLDGI.js +27 -0
- package/dist/{chunk-V2NUHPXF.js → chunk-S6M42KND.js} +1 -1
- package/dist/{chunk-7FTTULED.js → chunk-UQ77JXYH.js} +7 -3
- package/dist/{chunk-MAPZ7VB6.js → chunk-WNGFSIOB.js} +20 -1
- package/dist/{chunk-L2QUIZTS.js → chunk-ZIQWCZTS.js} +3 -3
- package/dist/{community-report-client-CU22W53K.js → community-report-client-PEERQCX2.js} +1 -1
- package/dist/{config-XO3LI3SA.js → config-FF6YVJKI.js} +29 -10
- package/dist/context-YAE3QXP3.js +136 -0
- package/dist/{data-integration-HNGUKFIC.js → data-integration-HESZET7E.js} +6 -6
- package/dist/index.js +169 -55
- package/dist/{local-data-upload-client-GTRIT64F.js → local-data-upload-client-TWMHFLUA.js} +1 -1
- package/dist/memory-62BJOULV.js +580 -0
- package/dist/{metadata-JAQW7KGZ.js → metadata-7CUMWIB4.js} +7 -7
- package/dist/{model-RNPQXIBI.js → model-SBV2JFJI.js} +9 -5
- package/dist/{personal-semantic-preference-QCZ2IZTK.js → personal-semantic-preference-6SRCYPGE.js} +119 -11
- package/dist/{project-semantic-64JURXKF.js → project-semantic-YYEM4OSU.js} +7 -7
- package/dist/{sync-O45NXEQY.js → sync-SU33KYX7.js} +12 -8
- package/dist/{te-agent-K5MSPNHJ.js → te-agent-F5QBZSQA.js} +423 -494
- package/dist/{te-analysis-KQKCAI5H.js → te-analysis-U4I2W2WC.js} +1090 -67
- package/dist/{te-community-4OYU3BDA.js → te-community-PFLDLPP4.js} +3 -3
- package/dist/{te-dataops-NFIPB6FX.js → te-dataops-FOQSRIAZ.js} +644 -276
- package/dist/{te-engage-HSVSRW22.js → te-engage-ZX76CNAB.js} +6 -6
- package/dist/{te-kb-3TS73NO6.js → te-kb-MHDOST62.js} +666 -244
- package/dist/{te-system-STMHLS5J.js → te-system-J2FQF5ER.js} +6 -6
- package/dist/{te-team-3I5AOX3N.js → te-team-5UOZPTU3.js} +6 -6
- package/dist/things-VPPXXLHB.js +1091 -0
- package/dist/{update-RZXLPCRH.js → update-EEUTEI6R.js} +59 -51
- package/dist/workspace-O3EEIYQM.js +554 -0
- package/package.json +30 -11
- package/skills/ae-agent/SKILL.md +95 -140
- package/skills/ae-agent/references/add-model.md +1 -1
- package/skills/ae-agent/references/create-agent.md +1 -1
- package/skills/ae-agent/references/find-archived-conversations.md +12 -5
- package/skills/ae-agent/references/get-agent.md +1 -1
- package/skills/ae-agent/references/list-agents.md +2 -2
- package/skills/ae-agent/references/list-models.md +2 -2
- package/skills/ae-agent/references/notification.md +40 -0
- package/skills/ae-agent/references/restore-conversation.md +3 -1
- package/skills/ae-analysis/SKILL.md +76 -15
- package/skills/ae-analysis/references/agent_review_submit_to_page.md +2 -0
- package/skills/ae-analysis/references/ai_models/event.md +6 -1
- package/skills/ae-analysis/references/ai_models/funnel.md +12 -4
- package/skills/ae-analysis/references/ai_models/heat_map.md +23 -0
- package/skills/ae-analysis/references/ai_models/interval.md +24 -0
- package/skills/ae-analysis/references/ai_models/path.md +20 -0
- package/skills/ae-analysis/references/ai_models/prop_analysis.md +2 -0
- package/skills/ae-analysis/references/ai_models/rank_list.md +30 -0
- package/skills/ae-analysis/references/ai_models/retention.md +45 -1
- package/skills/ae-analysis/references/ai_models/revenue.md +20 -2
- package/skills/ae-analysis/references/ai_models/tag.md +2 -0
- package/skills/ae-analysis/references/ai_models.md +96 -0
- package/skills/ae-analysis/references/analysis_data_retrieval.md +4 -0
- package/skills/ae-analysis/references/asset_batch_info_export.md +10 -0
- package/skills/ae-analysis/references/asset_batch_sql_export.md +13 -1
- package/skills/ae-analysis/references/asset_export.md +12 -1
- package/skills/ae-analysis/references/asset_list.md +1 -0
- package/skills/ae-analysis/references/asset_search.md +11 -5
- package/skills/ae-analysis/references/bi_panel_list.md +1 -1
- package/skills/ae-analysis/references/catalog_list.md +1 -1
- package/skills/ae-analysis/references/collaboration.md +48 -0
- package/skills/ae-analysis/references/command_index.md +26 -17
- package/skills/ae-analysis/references/dashboard_list.md +4 -4
- package/skills/ae-analysis/references/dashboard_move.md +21 -0
- package/skills/ae-analysis/references/dashboard_report_add.md +2 -0
- package/skills/ae-analysis/references/dashboard_report_data_export.md +2 -0
- package/skills/ae-analysis/references/dashboard_report_get.md +19 -0
- package/skills/ae-analysis/references/dashboard_report_update.md +27 -0
- package/skills/ae-analysis/references/dashboard_update.md +1 -1
- package/skills/ae-analysis/references/event_export.md +3 -1
- package/skills/ae-analysis/references/event_report_presentation.md +76 -0
- package/skills/ae-analysis/references/feedback_submit.md +37 -0
- package/skills/ae-analysis/references/governance_recommendation_auto_review.md +71 -0
- package/skills/ae-analysis/references/governance_recommendation_export.md +11 -0
- package/skills/ae-analysis/references/metadata_resolution.md +18 -3
- package/skills/ae-analysis/references/metric_export.md +3 -1
- package/skills/ae-analysis/references/metric_list.md +1 -1
- package/skills/ae-analysis/references/operation_record_export.md +10 -0
- package/skills/ae-analysis/references/personal_semantic_preference_add.md +2 -2
- package/skills/ae-analysis/references/personal_semantic_preference_get.md +6 -4
- package/skills/ae-analysis/references/personal_semantic_preference_list.md +5 -3
- package/skills/ae-analysis/references/personal_semantic_preference_review.md +9 -0
- package/skills/ae-analysis/references/personal_semantic_preference_review_list.md +7 -0
- package/skills/ae-analysis/references/personal_semantic_preference_submit.md +7 -0
- package/skills/ae-analysis/references/project_semantic_knowledge_wiki.md +91 -32
- package/skills/ae-analysis/references/project_semantic_knowledge_wiki_plan_schema.md +99 -1
- package/skills/ae-analysis/references/property_export.md +3 -1
- package/skills/ae-analysis/references/report_create.md +17 -3
- package/skills/ae-analysis/references/report_data_export.md +6 -0
- package/skills/ae-analysis/references/report_get.md +7 -1
- package/skills/ae-analysis/references/report_list.md +5 -5
- package/skills/ae-analysis/references/report_update.md +20 -3
- package/skills/ae-analysis/references/sql_table_columns.md +4 -4
- package/skills/ae-analysis/references/sql_table_list.md +5 -5
- package/skills/ae-analysis/references/system_query_monitor_overview.md +2 -0
- package/skills/ae-analysis/references/system_query_task_cancel.md +2 -0
- package/skills/ae-analysis/references/system_query_task_export.md +6 -2
- package/skills/ae-analysis/references/system_query_task_get.md +2 -0
- package/skills/ae-analysis/references/system_query_task_list.md +7 -3
- package/skills/ae-analysis/references/system_query_task_options.md +2 -2
- package/skills/ae-analysis/references/system_query_task_realtime_list.md +15 -0
- package/skills/ae-analysis/references/user_cluster_models.md +8 -0
- package/skills/ae-analysis/references/user_tag_create.md +7 -1
- package/skills/ae-analysis/references/user_tag_get.md +1 -1
- package/skills/ae-analysis/references/user_tag_models.md +20 -2
- package/skills/ae-analysis/references/user_tag_refresh.md +1 -1
- package/skills/ae-analysis/references/user_tag_update.md +2 -2
- package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/build-empty-skeleton.mjs +105 -0
- package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/build-project-semantic-wiki.mjs +669 -29
- package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/company-kb.mjs +21 -8
- package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/default-compile-rules.md +28 -10
- package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/default-compile-schema.md +66 -0
- package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/generate-build-ir.mjs +285 -24
- package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/package-wiki-source-zip.mjs +262 -30
- package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/plan-kb-source-sync.mjs +1 -1
- package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/precompiled-source.mjs +46 -2
- package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/prepare-compile-schema.mjs +64 -0
- package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/refresh-state-parts.mjs +91 -0
- package/skills/ae-automation/SKILL.md +16 -0
- package/skills/ae-automation/references/cli.md +53 -0
- package/skills/ae-automation/references/command_index.md +17 -0
- package/skills/ae-capability/SKILL.md +73 -2
- package/skills/ae-capability/references/collaboration.md +48 -0
- package/skills/ae-community/SKILL.md +5 -1
- package/skills/ae-community/references/collaboration.md +48 -0
- package/skills/ae-current-context/SKILL.md +3 -2
- package/skills/ae-data-integration/SKILL.md +4 -0
- package/skills/ae-data-integration/references/collaboration.md +48 -0
- package/skills/ae-dataops/SKILL.md +54 -74
- package/skills/ae-dataops/references/collaboration.md +48 -0
- package/skills/ae-dataops/references/dataops-backfill.md +23 -18
- package/skills/ae-dataops/references/dataops-flow-create.md +74 -23
- package/skills/ae-dataops/references/dataops-flow-monitor.md +45 -13
- package/skills/ae-dataops/references/dataops-integration.md +57 -30
- package/skills/ae-dataops/references/dataops-query.md +21 -4
- package/skills/ae-dataops/references/dataops-table.md +143 -11
- package/skills/ae-engage/SKILL.md +21 -6
- package/skills/ae-engage/references/channel-mgmt.md +3 -3
- package/skills/ae-engage/references/collaboration.md +48 -0
- package/skills/ae-engage/references/scene-config-channel.md +4 -4
- package/skills/ae-generate-tracking-plan/SKILL.md +4 -0
- package/skills/ae-generate-tracking-plan/references/collaboration.md +48 -0
- package/skills/ae-kb/SKILL.md +70 -12
- package/skills/ae-kb/references/analysis-workflow.md +73 -36
- package/skills/ae-kb/references/collaboration.md +48 -0
- package/skills/ae-kb/references/query-workflow.md +67 -33
- package/skills/ae-kb/references/schema-import.md +32 -0
- package/skills/ae-kb/references/source-mutations.md +6 -4
- package/skills/ae-kb/references/text-sources.md +53 -0
- package/skills/ae-kb-discovery/SKILL.md +8 -3
- package/skills/ae-kb-discovery/references/collaboration.md +48 -0
- package/skills/ae-metadata/SKILL.md +20 -12
- package/skills/ae-metadata/references/collaboration.md +48 -0
- package/skills/ae-team/SKILL.md +23 -17
- package/skills/ae-things/SKILL.md +85 -0
- package/skills/ae-things/references/cli.md +103 -0
- package/skills/ae-things-progress/SKILL.md +43 -0
- package/skills/ae-things-review/SKILL.md +42 -0
- package/skills/ae-workspace/SKILL.md +43 -0
- package/skills/ae-workspace/references/commands.md +51 -0
- package/dist/chunk-E2JKXMVO.js +0 -29
- package/dist/context-IFJI3LIT.js +0 -46
- package/dist/memory-QGPSNCQS.js +0 -892
- package/skills/ae-agent/references/create-automation.md +0 -125
- package/skills/ae-agent/references/list-automations.md +0 -56
- package/skills/ae-agent/references/update-automation.md +0 -107
package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/package-wiki-source-zip.mjs
CHANGED
|
@@ -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 (
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
Object.assign(result, {
|
|
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 =
|
|
80
|
-
name:
|
|
81
|
-
content: fs.readFileSync(path.join(treeOutput,
|
|
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.
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
|
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(
|
|
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
|
+
}
|