@thinkingai/ae-cli 6.1.24 → 6.1.25
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 +7 -1
- package/README.zh.md +7 -1
- package/dist/{auth-XZNXFRJS.js → auth-LHM7NQPR.js} +167 -7
- package/dist/{capability-FYTU3X6L.js → capability-SJI5KOCK.js} +11 -6
- package/dist/{chunk-CCS66K4K.js → chunk-2FJM4HRV.js} +3 -3
- package/dist/{chunk-57RRFUEZ.js → chunk-5XRQ3CZR.js} +4 -4
- package/dist/{chunk-EQ255HKO.js → chunk-7WZACOAI.js} +2 -2
- package/dist/{chunk-DX5CDO34.js → chunk-BW7JUDEI.js} +84 -19
- package/dist/{chunk-7XGFSUOM.js → chunk-DU72X2IO.js} +271 -76
- package/dist/{chunk-V2NUHPXF.js → chunk-GK7WDY7E.js} +1 -1
- package/dist/{chunk-7FTTULED.js → chunk-IG3DYEIR.js} +4 -1
- package/dist/{chunk-HBGADCKA.js → chunk-KJDOTPYU.js} +3 -3
- package/dist/{chunk-4NRCTUZJ.js → chunk-KS4DE3MX.js} +4 -4
- package/dist/{chunk-JOCDD4ON.js → chunk-N4BLPJC7.js} +1 -1
- package/dist/{chunk-HFGZGYCG.js → chunk-QYXDJOLA.js} +252 -6
- package/dist/{chunk-DVMLWQD4.js → chunk-SO5LKIWP.js} +2 -2
- package/dist/{chunk-VYXXOKYS.js → chunk-XKIW3QMF.js} +7 -4
- package/dist/{chunk-MAPZ7VB6.js → chunk-YVGXGTD6.js} +13 -0
- package/dist/{community-report-client-CU22W53K.js → community-report-client-6VJDDMJJ.js} +1 -1
- package/dist/{config-XO3LI3SA.js → config-XQWOPDHN.js} +25 -6
- package/dist/{context-IFJI3LIT.js → context-M3HDGEIR.js} +4 -4
- package/dist/{data-integration-HNGUKFIC.js → data-integration-XBTCGXQT.js} +6 -6
- package/dist/index.js +78 -41
- package/dist/{local-data-upload-client-GTRIT64F.js → local-data-upload-client-3P7NIVQ4.js} +1 -1
- package/dist/{memory-QGPSNCQS.js → memory-6APEB2JB.js} +4 -4
- package/dist/{metadata-DI5BMYFU.js → metadata-JRYKNUS5.js} +7 -7
- package/dist/{model-RNPQXIBI.js → model-ZS7HVGKN.js} +4 -4
- package/dist/{personal-semantic-preference-5C3WUXFI.js → personal-semantic-preference-IDE2GE5T.js} +8 -6
- package/dist/project-semantic-36KOCYN7.js +356 -0
- package/dist/{sync-O45NXEQY.js → sync-EQLSTUS7.js} +7 -7
- package/dist/{te-agent-K5MSPNHJ.js → te-agent-Z3HCTKR7.js} +348 -55
- package/dist/{te-analysis-AISWCOOZ.js → te-analysis-5ZZ6YHVH.js} +940 -144
- package/dist/{te-community-4OYU3BDA.js → te-community-Z7JACGF3.js} +3 -3
- package/dist/{te-dataops-NFIPB6FX.js → te-dataops-F7GQOIS6.js} +644 -276
- package/dist/{te-engage-BAM3GBFX.js → te-engage-RNRMYDO3.js} +6 -6
- package/dist/{te-experiment-GUTZXZ5Z.js → te-experiment-BTAC2UNK.js} +6 -6
- package/dist/{te-kb-3TS73NO6.js → te-kb-QJLJEX5K.js} +323 -181
- package/dist/{te-system-STMHLS5J.js → te-system-IXC43QHZ.js} +6 -6
- package/dist/{te-team-3I5AOX3N.js → te-team-BIZIKIHZ.js} +6 -6
- package/dist/{update-RZXLPCRH.js → update-W2SGQNKT.js} +4 -4
- package/package.json +11 -4
- package/skills/ae-agent/SKILL.md +9 -4
- package/skills/ae-agent/references/notification.md +40 -0
- package/skills/ae-analysis/SKILL.md +43 -20
- package/skills/ae-analysis/references/adhoc_export.md +1 -1
- package/skills/ae-analysis/references/adhoc_run.md +1 -1
- 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 +7 -1
- 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/session.md +76 -0
- package/skills/ae-analysis/references/ai_models/tag.md +2 -0
- package/skills/ae-analysis/references/ai_models.md +73 -3
- package/skills/ae-analysis/references/analysis_data_retrieval.md +4 -0
- package/skills/ae-analysis/references/analysis_drilldown_contract.md +4 -1
- 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 +19 -17
- package/skills/ae-analysis/references/dashboard_list.md +4 -4
- package/skills/ae-analysis/references/dashboard_report_data_export.md +2 -0
- package/skills/ae-analysis/references/drilldown_session_details_run.md +41 -0
- package/skills/ae-analysis/references/event_export.md +3 -1
- package/skills/ae-analysis/references/governance_recommendation_auto_review.md +77 -0
- package/skills/ae-analysis/references/governance_recommendation_export.md +11 -0
- package/skills/ae-analysis/references/metadata_resolution.md +3 -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 +4 -4
- package/skills/ae-analysis/references/personal_semantic_preference_list.md +4 -4
- package/skills/ae-analysis/references/project_semantic_knowledge_wiki.md +29 -11
- package/skills/ae-analysis/references/project_semantic_knowledge_wiki_plan_schema.md +59 -0
- package/skills/ae-analysis/references/property_export.md +3 -1
- package/skills/ae-analysis/references/report_create.md +5 -1
- package/skills/ae-analysis/references/report_data_export.md +6 -0
- package/skills/ae-analysis/references/report_get.md +2 -0
- package/skills/ae-analysis/references/report_list.md +5 -5
- package/skills/ae-analysis/references/report_update.md +7 -1
- 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/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 +18 -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-project-semantic-wiki.mjs +337 -12
- package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/default-compile-rules.md +7 -2
- package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/generate-build-ir.mjs +284 -24
- package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/package-wiki-source-zip.mjs +185 -22
- package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/precompiled-source.mjs +46 -2
- package/skills/ae-capability/SKILL.md +63 -1
- 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-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 +9 -1
- package/skills/ae-kb/references/collaboration.md +48 -0
- package/skills/ae-kb/references/schema-import.md +30 -0
- package/skills/ae-kb-discovery/SKILL.md +7 -2
- 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/dist/project-semantic-3LF6Q6JZ.js +0 -1119
- package/skills/ae-project-semantic/SKILL.md +0 -193
- package/skills/ae-project-semantic/references/query-routing-v5.md +0 -165
- package/skills/ae-project-semantic/references/recommendation-quality.md +0 -68
|
@@ -4,7 +4,7 @@ Get exact user tag details after discovery.
|
|
|
4
4
|
|
|
5
5
|
Do not use it for fuzzy discovery, member data, or history statistics. Output contains saved tag metadata/definition for the requested names.
|
|
6
6
|
|
|
7
|
-
After `user-tag create` or an update
|
|
7
|
+
After `user-tag create` or an update reporting `computation.triggered_automatically=true`, use this command to observe the automatically started computation; do not trigger `user-tag refresh`. For create, wait until `progress=100` and `refresh_time` is present. For a changed definition, record the previous `refresh_time` first and wait until `progress=100` and `refresh_time` advances. Until then, `users_num` and member queries may still reflect the previous successful computation; treat that as stale data, not as evidence that the new definition was compiled or calculated incorrectly. An identical definition is not recomputed; when update returns `not_triggered` and `next_action=none`, do not wait for a new refresh timestamp.
|
|
8
8
|
|
|
9
9
|
The raw backend value is returned under `result`. The response also provides aggregate and per-tag `computation`, `result_freshness`, and `next_action`. `poll_get` includes an exact capability/input pair; `none` means the latest result is usable. `potentially_stale` with `is_stale=null` means `refresh_time` predates `update_time`, but the update may have changed metadata only; inspect the latest update instead of refreshing automatically.
|
|
10
10
|
|
|
@@ -24,7 +24,9 @@ Top-level `type` is exactly one of `condition`, `metric`, `first_last`, or `sql`
|
|
|
24
24
|
|
|
25
25
|
## Metric tag
|
|
26
26
|
|
|
27
|
-
Required: `event`, `aggregation`. `property`, `percentile`,
|
|
27
|
+
Required: `event`, `aggregation`, and `time_range`. `property`, `percentile`, and `filters` are optional. Filters support only event properties and user properties. A string `field` is an event property; use `{name,type:"user_property"}` for a user property. Use the semantic time mappings in [`audience_models.md`](audience_models.md); never omit `time_range` from a metric tag.
|
|
28
|
+
|
|
29
|
+
Supported aggregations are `count`, `active_days`, `sum`, `avg`, `max`, `min`, `median`, and `percentile`. `median` is a dedicated aggregation for a numeric event `property`; do not represent it as `percentile=50`.
|
|
28
30
|
|
|
29
31
|
Use `aggregation=percentile` with a numeric event `property` and pass `percentile`. Supported percentile values match the page controls: `5`, `10`, `20`, `25`, `30`, `40`, `60`, `70`, `75`, `80`, `90`, `95`, and `99`. The `percentile` field is required for percentile aggregation and is rejected for every other aggregation.
|
|
30
32
|
|
|
@@ -32,10 +34,16 @@ Use `aggregation=percentile` with a numeric event `property` and pass `percentil
|
|
|
32
34
|
{"type":"metric","metric":{"event":"pay","aggregation":"sum","property":"amount","time_range":{"mode":"previous","unit":"day","value":30},"filters":{"relation":"and","items":[{"field":"channel","operator":"eq","values":["app"]},{"field":{"name":"country","type":"user_property"},"operator":"eq","values":["US"]}]}}}
|
|
33
35
|
```
|
|
34
36
|
|
|
37
|
+
Median example:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{"type":"metric","metric":{"event":"pay","aggregation":"median","property":"amount","time_range":{"mode":"previous","unit":"day","value":30}}}
|
|
41
|
+
```
|
|
42
|
+
|
|
35
43
|
Percentile example:
|
|
36
44
|
|
|
37
45
|
```json
|
|
38
|
-
{"type":"metric","metric":{"event":"pay","aggregation":"percentile","property":"amount","percentile":90}}
|
|
46
|
+
{"type":"metric","metric":{"event":"pay","aggregation":"percentile","property":"amount","percentile":90,"time_range":{"mode":"previous","unit":"day","value":30}}}
|
|
39
47
|
```
|
|
40
48
|
|
|
41
49
|
## First/last tag
|
|
@@ -61,8 +69,16 @@ From a fixed date through today:
|
|
|
61
69
|
SQL tags support only `${PartDate:name}` dynamic placeholders. Every placeholder requires one `params` item with `type=part_date` and either `recent_day` or `start_time` plus `end_time`. `Text`, `Number`, `Variable`, `Time`, and `Selector` parameters are valid for general SQL analysis but are not valid for SQL tags.
|
|
62
70
|
For `recent_day`, `0-7` means 最近7天 and includes today; `1-7` means 过去7天 and excludes today.
|
|
63
71
|
|
|
72
|
+
`${base_date}` is a separate built-in computation-date macro, not a user-configurable dynamic placeholder. SQL tags may use `${base_date}`, a formatted value such as `${base_date:yyyy-MM-dd}`, or a server-supported offset such as `${base_date:yyyy-MM-dd-1*day}`. Keep the macro in `sql` and do not add it to `params`; it is expanded when the tag is calculated. Expansion produces plain text, so quote the macro when the SQL expression expects a string or date literal:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{"type":"sql","sql":"SELECT \"#user_id\", 'payer' AS tag_value FROM hive.ta.v_event_1 WHERE \"$part_date\"='${base_date:yyyy-MM-dd}' AND \"$part_event\"='pay'"}
|
|
76
|
+
```
|
|
77
|
+
|
|
64
78
|
Before writing a SQL tag, use `analysis sql-table list --project-id <project_id> --usage tag_cluster`, then call `analysis sql-table columns --project-id <project_id> --table-ref <table_ref> --usage tag_cluster`. The `tag_cluster` authorized table set is intentionally different from the general analysis SQL table set; do not reuse a table discovered with the default `usage=analysis`.
|
|
65
79
|
|
|
66
80
|
`#user_id`, `$part_event`, and any other Trino identifier containing `#`, `$`, `@`, spaces, or punctuation must be delimited with double quotes. Single quotes are only for values.
|
|
67
81
|
|
|
68
82
|
When the SQL reads an event table, it must also filter the quoted `"$part_date"` date-partition column; the backend rejects event-table SQL without that predicate.
|
|
83
|
+
|
|
84
|
+
For space tables, use the returned `sql_reference` when writing SQL and loading columns. `source_type=gaia` identifies a space table; keep `--usage tag_cluster` for both discovery commands.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Explicitly recompute an existing user tag by exact `tag_name` without changing its definition.
|
|
4
4
|
|
|
5
|
-
Use this only when an unchanged definition must be run again, such as an explicit retry or a recomputation against changed source data. Do not call it after `user-tag create` or an update
|
|
5
|
+
Use this only when an unchanged definition must be run again, such as an explicit retry or a recomputation against changed source data. Do not call it after `user-tag create` or an update reporting `computation.triggered_automatically=true`; those commands already start computation. An identical definition update does not trigger computation and does not by itself authorize a separate refresh. Do not use this command to refresh one history snapshot. Output means recomputation was submitted, not completed. Poll `user-tag get` and verify the new computation before using `users_num` or querying members.
|
|
6
6
|
|
|
7
7
|
The response marks this as an explicit trigger: `computation.triggered_by_command=true`, `computation.triggered_automatically=false`, `computation.status=submitted`, and `result_freshness.is_stale=true`. Follow the returned `next_action`, `next_capability_id`, and `next_input` to poll.
|
|
8
8
|
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Update a user tag. Discover the exact `tag_name` first.
|
|
4
4
|
|
|
5
|
-
Do not use it for ID-file value replacement or to create a missing tag.
|
|
5
|
+
Do not use it for ID-file value replacement or to create a missing tag. A changed `--definition-request` automatically starts recomputation; the native service skips recomputation when the normalized definition and timezone are unchanged. Follow the returned `computation.triggered_automatically` and `next_action`; do not call `user-tag refresh` afterward. Updating only `--display-name` or `--remark` does not recompute. A schedule-only update changes periodic refresh without starting immediate recomputation. `--auto-refresh-cron` or `--auto-refresh-schedule` can enable periodic refresh directly, including on a previously disabled tag. A successful update means the definition was saved, not that a triggered computation is complete. When computation was triggered, poll `user-tag get` until `progress=100` and `refresh_time` advances from the value recorded before the update. An unchanged definition returns `not_triggered` and `next_action=none`; do not wait for its refresh timestamp to change.
|
|
6
6
|
|
|
7
|
-
The response distinguishes both paths.
|
|
7
|
+
The response distinguishes both paths. An update that triggers recomputation returns `computation.triggered_automatically=true`, `result_freshness.is_stale=true`, and `next_action=poll_get` with an exact capability/input pair. An unchanged definition or display-name/remark-only update returns `computation.status=not_triggered`, `result_freshness.status=fresh`, and `next_action=none`.
|
|
8
8
|
|
|
9
9
|
Flags: `--project-id`, `--tag-name` required. Optional: `--display-name`, `--definition-request`, `--authenticated-only`, `--remark`, `--zone-offset`, `--enable-auto-refresh`, `--auto-refresh-schedule`, `--auto-refresh-cron`. The tag type comes from `definition_request.type` when the definition changes.
|
|
10
10
|
|
package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/build-project-semantic-wiki.mjs
CHANGED
|
@@ -23,6 +23,7 @@ const projectNameOverride = clean(args['project-name']);
|
|
|
23
23
|
const force = Boolean(args.force);
|
|
24
24
|
const keepBuildIr = Boolean(args['keep-build-ir']);
|
|
25
25
|
const allowAllVisible = Boolean(args['allow-all-visible']);
|
|
26
|
+
const allowSemanticPlanSnapshotDrift = Boolean(args['allow-semantic-plan-snapshot-drift']);
|
|
26
27
|
|
|
27
28
|
assertSafePaths(packageRoot, outputRoot, archivePath, uploadSourcesPath);
|
|
28
29
|
|
|
@@ -59,7 +60,9 @@ const snapshotDate = normalizeSnapshotDate(args['snapshot-date'] || descriptor.g
|
|
|
59
60
|
|
|
60
61
|
const semanticPlan = readJson(semanticPlanPath);
|
|
61
62
|
if (clean(semanticPlan.schema_version) !== '2.0') fail('Project semantic knowledge Wiki plan schema 2.0 is required.');
|
|
62
|
-
if (clean(semanticPlan.source_snapshot_hash) !== snapshotHash
|
|
63
|
+
if (clean(semanticPlan.source_snapshot_hash) !== snapshotHash && !allowSemanticPlanSnapshotDrift) {
|
|
64
|
+
fail('Semantic plan snapshot hash does not match the asset package.');
|
|
65
|
+
}
|
|
63
66
|
if (clean(semanticPlan.generation_method) !== 'agent_semantic_synthesis') {
|
|
64
67
|
fail('Semantic plan generation_method must be agent_semantic_synthesis.');
|
|
65
68
|
}
|
|
@@ -84,6 +87,7 @@ for (const domain of domains) for (const id of asArray(domain.dashboard_ids).map
|
|
|
84
87
|
for (const appendix of appendices) for (const id of asArray(appendix.dashboard_ids).map(clean)) appendixByDashboardId.set(id, appendix);
|
|
85
88
|
|
|
86
89
|
const planModel = validateSemanticPlan();
|
|
90
|
+
const semanticPlanState = buildSemanticPlanState();
|
|
87
91
|
const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'project-semantic-knowledge-wiki-'));
|
|
88
92
|
const buildIr = path.join(scratch, 'build-ir');
|
|
89
93
|
|
|
@@ -92,6 +96,7 @@ try {
|
|
|
92
96
|
const generatorArgs = [generator, '--asset-package', packageRoot, '--domain-plan', semanticPlanPath,
|
|
93
97
|
'--output', buildIr, '--project-name', projectName];
|
|
94
98
|
if (allowAllVisible) generatorArgs.push('--allow-all-visible');
|
|
99
|
+
if (allowSemanticPlanSnapshotDrift) generatorArgs.push('--allow-semantic-plan-snapshot-drift');
|
|
95
100
|
execFileSync(process.execPath, generatorArgs, { stdio: 'pipe' });
|
|
96
101
|
|
|
97
102
|
const corpus = readJson(path.join(buildIr, 'corpus.json'));
|
|
@@ -117,7 +122,7 @@ try {
|
|
|
117
122
|
const id = clean(domain.domain_id);
|
|
118
123
|
const source = readText(path.join(buildIr, 'domains', fileName('domain', id)));
|
|
119
124
|
const cards = asArray(domain.recall_cards);
|
|
120
|
-
const cardLinks = cards.map((card) => `- [${escapeLink(card.
|
|
125
|
+
const cardLinks = cards.map((card) => `- [${escapeLink(card.intent || card.card_id)}](../recall-cards/${fileName('recall-card', card.card_id)})`).join('\n');
|
|
121
126
|
writeText(path.join(wikiRoot, 'domains', fileName('domain', id)),
|
|
122
127
|
replaceFrontmatter(source, domainFrontmatter(domain))
|
|
123
128
|
+ `\n## 场景召回卡\n\n${cardLinks || '- 无'}\n`);
|
|
@@ -201,6 +206,7 @@ try {
|
|
|
201
206
|
writeText(path.join(wikiRoot, 'index.md'), renderWikiIndex(corpus));
|
|
202
207
|
writeText(path.join(wikiRoot, 'log.md'), renderLog(corpus));
|
|
203
208
|
writeText(path.join(staging, 'index.md'), renderSnapshotIndex(corpus));
|
|
209
|
+
writeText(path.join(staging, 'refresh-state.md'), renderRefreshState(semanticPlanState));
|
|
204
210
|
|
|
205
211
|
const sourceProjectRefs = sourceProjectReferencesFromPackage();
|
|
206
212
|
const sourceProjectIds = [...new Set(sourceProjectRefs.map((row) => row.source_project_id))].sort((a, b) => a - b);
|
|
@@ -215,7 +221,9 @@ try {
|
|
|
215
221
|
source_project_id_status: sourceProjectRefs.length ? 'detected_in_packaged_definitions' : 'not_exposed',
|
|
216
222
|
project_name: projectName,
|
|
217
223
|
source_snapshot_hash: snapshotHash,
|
|
218
|
-
semantic_plan_hash:
|
|
224
|
+
semantic_plan_hash: semanticPlanState.plan_hash,
|
|
225
|
+
semantic_plan_state_schema_version: semanticPlanState.schema_version,
|
|
226
|
+
semantic_plan_fragment_count: semanticPlanState.fragments.length,
|
|
219
227
|
asset_package_schema_version: '3.0',
|
|
220
228
|
asset_scope: packageAssetScope,
|
|
221
229
|
wiki_schema_version: '1.0',
|
|
@@ -265,6 +273,8 @@ function validateSemanticPlan() {
|
|
|
265
273
|
const cardIds = new Set();
|
|
266
274
|
const excludedByAsset = new Map();
|
|
267
275
|
const preferredByAsset = new Map();
|
|
276
|
+
const domainIds = new Set(domains.map((domain) => clean(domain.domain_id)).filter(Boolean));
|
|
277
|
+
const projectBusinessModel = validateProjectBusinessModel(semanticPlan.project_business_model, domainIds);
|
|
268
278
|
for (const domain of domains) {
|
|
269
279
|
const domainId = clean(domain.domain_id);
|
|
270
280
|
const domainDashboardIds = new Set(asArray(domain.dashboard_ids).map(clean));
|
|
@@ -272,6 +282,7 @@ function validateSemanticPlan() {
|
|
|
272
282
|
.some((ref) => domainDashboardIds.has(clean(ref.dashboard_id)))).map((report) => clean(report.resource_key)));
|
|
273
283
|
const domainMetricIds = new Set(reports.filter((report) => domainReportIds.has(clean(report.resource_key)))
|
|
274
284
|
.flatMap((report) => asArray(report.metric_occurrences).map((item) => clean(item.metric_key)).filter(Boolean)));
|
|
285
|
+
validateDomainJudgmentModel(domain.domain_judgment_model, domainId, domainDashboardIds, domainReportIds, domainMetricIds);
|
|
275
286
|
const recallCards = asArray(domain.recall_cards);
|
|
276
287
|
if (!recallCards.length) fail(`Domain ${domainId || '<unknown>'} must contain at least one recall card.`);
|
|
277
288
|
for (const value of recallCards) {
|
|
@@ -334,7 +345,138 @@ function validateSemanticPlan() {
|
|
|
334
345
|
fail(`Semantic plan is missing SQL semantics for ${missingSql.length} report(s): ${missingSql.join(', ')}`);
|
|
335
346
|
}
|
|
336
347
|
|
|
337
|
-
return {
|
|
348
|
+
return {
|
|
349
|
+
cards,
|
|
350
|
+
excludedByAsset,
|
|
351
|
+
preferredByAsset,
|
|
352
|
+
sqlByReportId,
|
|
353
|
+
missingSqlByReportId: new Set(missingSql),
|
|
354
|
+
projectBusinessModel,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function validateProjectBusinessModel(value, domainIds) {
|
|
359
|
+
const model = asObject(value);
|
|
360
|
+
const businessPositioning = clean(model.business_positioning);
|
|
361
|
+
const audienceRoles = asArray(model.audience_roles).map(clean).filter(Boolean);
|
|
362
|
+
const businessObjects = asArray(model.business_objects).map((item, index) => {
|
|
363
|
+
const row = asObject(item);
|
|
364
|
+
const objectId = clean(row.object_id);
|
|
365
|
+
const name = clean(row.name);
|
|
366
|
+
const meaning = clean(row.meaning);
|
|
367
|
+
if (!/^[a-z0-9][a-z0-9_-]{1,79}$/.test(objectId)) fail(`project_business_model.business_objects[${index}] has invalid object_id.`);
|
|
368
|
+
if (!name || !meaning) fail(`project_business_model.business_objects[${index}] requires name and meaning.`);
|
|
369
|
+
const evidenceRefs = normalizePlanAssetRefs(row.evidence_refs, `business object ${objectId}`);
|
|
370
|
+
if (!evidenceRefs.length) fail(`business object ${objectId} requires evidence_refs.`);
|
|
371
|
+
return { object_id: objectId, name, meaning, evidence_refs: evidenceRefs };
|
|
372
|
+
});
|
|
373
|
+
const objectNames = new Set(businessObjects.map((item) => item.name));
|
|
374
|
+
const objectRelationships = asArray(model.object_relationships).map((item, index) => {
|
|
375
|
+
const row = asObject(item);
|
|
376
|
+
const fromObject = clean(row.from_object);
|
|
377
|
+
const toObject = clean(row.to_object);
|
|
378
|
+
const relationship = clean(row.relationship);
|
|
379
|
+
if (!fromObject || !toObject || !relationship) {
|
|
380
|
+
fail(`project_business_model.object_relationships[${index}] requires from_object, to_object and relationship.`);
|
|
381
|
+
}
|
|
382
|
+
if (!objectNames.has(fromObject) || !objectNames.has(toObject)) {
|
|
383
|
+
fail(`object relationship ${fromObject || '<empty>'}->${toObject || '<empty>'} must reference declared business object names.`);
|
|
384
|
+
}
|
|
385
|
+
const evidenceRefs = normalizePlanAssetRefs(row.evidence_refs, `object relationship ${fromObject}->${toObject}`);
|
|
386
|
+
if (!evidenceRefs.length) fail(`object relationship ${fromObject}->${toObject} requires evidence_refs.`);
|
|
387
|
+
return { from_object: fromObject, to_object: toObject, relationship, evidence_refs: evidenceRefs };
|
|
388
|
+
});
|
|
389
|
+
const decisionChains = asArray(model.decision_chains).map((item, index) => {
|
|
390
|
+
const row = asObject(item);
|
|
391
|
+
const chainId = clean(row.chain_id);
|
|
392
|
+
const title = clean(row.title);
|
|
393
|
+
const decisionQuestion = clean(row.decision_question);
|
|
394
|
+
const signals = asArray(row.signals).map(clean).filter(Boolean);
|
|
395
|
+
const decisionSteps = asArray(row.decision_steps).map(clean).filter(Boolean);
|
|
396
|
+
const preferredDomainIds = asArray(row.preferred_domain_ids).map(clean).filter(Boolean);
|
|
397
|
+
const assetRefs = normalizePlanAssetRefs(row.asset_refs, `decision chain ${chainId || index + 1}`);
|
|
398
|
+
const boundaries = asArray(row.boundaries).map(clean).filter(Boolean);
|
|
399
|
+
if (!/^[a-z0-9][a-z0-9_-]{1,79}$/.test(chainId)) fail(`project_business_model.decision_chains[${index}] has invalid chain_id.`);
|
|
400
|
+
if (!title || !decisionQuestion) fail(`decision chain ${chainId} requires title and decision_question.`);
|
|
401
|
+
if (signals.length < 2) fail(`decision chain ${chainId} requires at least two signals.`);
|
|
402
|
+
if (decisionSteps.length < 2) fail(`decision chain ${chainId} requires at least two decision_steps.`);
|
|
403
|
+
if (!preferredDomainIds.length || preferredDomainIds.some((id) => !domainIds.has(id))) {
|
|
404
|
+
fail(`decision chain ${chainId} must reference existing preferred_domain_ids.`);
|
|
405
|
+
}
|
|
406
|
+
if (!assetRefs.length) fail(`decision chain ${chainId} requires asset_refs.`);
|
|
407
|
+
if (!boundaries.length) fail(`decision chain ${chainId} requires boundaries.`);
|
|
408
|
+
return {
|
|
409
|
+
chain_id: chainId,
|
|
410
|
+
title,
|
|
411
|
+
decision_question: decisionQuestion,
|
|
412
|
+
signals,
|
|
413
|
+
decision_steps: decisionSteps,
|
|
414
|
+
preferred_domain_ids: preferredDomainIds,
|
|
415
|
+
asset_refs: assetRefs,
|
|
416
|
+
requires_live_execution: Boolean(row.requires_live_execution),
|
|
417
|
+
boundaries,
|
|
418
|
+
};
|
|
419
|
+
});
|
|
420
|
+
const nonGoals = asArray(model.non_goals).map(clean).filter(Boolean);
|
|
421
|
+
if (!businessPositioning) fail('project_business_model requires business_positioning.');
|
|
422
|
+
if (!audienceRoles.length) fail('project_business_model requires audience_roles.');
|
|
423
|
+
if (!businessObjects.length) fail('project_business_model requires business_objects.');
|
|
424
|
+
if (!objectRelationships.length) fail('project_business_model requires object_relationships.');
|
|
425
|
+
if (!decisionChains.length) fail('project_business_model requires decision_chains.');
|
|
426
|
+
if (!nonGoals.length) fail('project_business_model requires non_goals.');
|
|
427
|
+
return {
|
|
428
|
+
business_positioning: businessPositioning,
|
|
429
|
+
audience_roles: audienceRoles,
|
|
430
|
+
business_objects: businessObjects,
|
|
431
|
+
object_relationships: objectRelationships,
|
|
432
|
+
decision_chains: decisionChains,
|
|
433
|
+
non_goals: nonGoals,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function validateDomainJudgmentModel(value, domainId, domainDashboardIds, domainReportIds, domainMetricIds) {
|
|
438
|
+
const model = asObject(value);
|
|
439
|
+
const businessStateJudged = clean(model.business_state_judged);
|
|
440
|
+
const mainObjects = asArray(model.main_objects).map(clean).filter(Boolean);
|
|
441
|
+
const signals = asArray(model.signals).map(clean).filter(Boolean);
|
|
442
|
+
const decisionPath = asArray(model.decision_path).map(clean).filter(Boolean);
|
|
443
|
+
const boundaries = asArray(model.boundaries).map(clean).filter(Boolean);
|
|
444
|
+
const attachmentLogic = asArray(model.asset_attachment_logic);
|
|
445
|
+
if (!businessStateJudged) fail(`Domain ${domainId} is missing domain_judgment_model.business_state_judged.`);
|
|
446
|
+
if (!mainObjects.length) fail(`Domain ${domainId} is missing domain_judgment_model.main_objects.`);
|
|
447
|
+
if (signals.length < 2) fail(`Domain ${domainId} domain_judgment_model requires at least two signals.`);
|
|
448
|
+
if (decisionPath.length < 2) fail(`Domain ${domainId} domain_judgment_model requires at least two decision_path steps.`);
|
|
449
|
+
if (!attachmentLogic.length) fail(`Domain ${domainId} domain_judgment_model requires asset_attachment_logic.`);
|
|
450
|
+
if (!boundaries.length) fail(`Domain ${domainId} domain_judgment_model requires boundaries.`);
|
|
451
|
+
for (const [index, item] of attachmentLogic.entries()) {
|
|
452
|
+
const row = asObject(item);
|
|
453
|
+
const ref = normalizePlanAssetRef(row.asset_ref, `domain ${domainId} asset_attachment_logic[${index}]`);
|
|
454
|
+
const role = clean(row.role);
|
|
455
|
+
const reason = clean(row.reason);
|
|
456
|
+
if (!['primary_entry', 'supporting_evidence', 'drilldown', 'exclusion'].includes(role)) {
|
|
457
|
+
fail(`domain ${domainId} asset_attachment_logic[${index}] has invalid role.`);
|
|
458
|
+
}
|
|
459
|
+
if (!reason || reason.length < 10) fail(`domain ${domainId} asset_attachment_logic[${index}] requires concrete reason.`);
|
|
460
|
+
const inDomain = ref.resource_type === 'dashboard' ? domainDashboardIds.has(ref.resource_key)
|
|
461
|
+
: ref.resource_type === 'report' ? domainReportIds.has(ref.resource_key)
|
|
462
|
+
: ref.resource_type === 'metric' ? domainMetricIds.has(ref.resource_key) : false;
|
|
463
|
+
if (!inDomain) fail(`domain ${domainId} asset_attachment_logic references ${ref.resource_type}:${ref.resource_key} outside domain closure.`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function normalizePlanAssetRefs(values, label) {
|
|
468
|
+
return asArray(values).map((value, index) => normalizePlanAssetRef(value, `${label} asset_refs[${index}]`));
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function normalizePlanAssetRef(value, label) {
|
|
472
|
+
const ref = asObject(value);
|
|
473
|
+
const type = clean(ref.resource_type);
|
|
474
|
+
const key = clean(ref.resource_key);
|
|
475
|
+
const exists = type === 'dashboard' ? dashboardById.has(key)
|
|
476
|
+
: type === 'report' ? reportById.has(key)
|
|
477
|
+
: type === 'metric' ? metricById.has(key) : false;
|
|
478
|
+
if (!exists) fail(`${label} references unknown asset ${type || '<type>'}:${key || '<key>'}.`);
|
|
479
|
+
return { resource_type: type, resource_key: key };
|
|
338
480
|
}
|
|
339
481
|
|
|
340
482
|
function assertAgentAuthoredRecallCard(card, domainId) {
|
|
@@ -436,8 +578,36 @@ function writeMergedAsset(wikiRoot, directory, asset, authority, extra = '') {
|
|
|
436
578
|
const brief = readText(path.join(buildIr, 'briefs', directory, fileName(directory.slice(0, -1), id)));
|
|
437
579
|
const type = directory === 'dashboards' ? 'project-dashboard' : directory === 'reports' ? 'project-report' : 'project-reusable-metric';
|
|
438
580
|
const title = assetReadableTitle(asset, directory.slice(0, -1));
|
|
581
|
+
const authorityNotice = renderAssetAuthorityNotice(authority);
|
|
582
|
+
const mergedExtra = [authorityNotice, extra.trim()].filter(Boolean).join('\n\n');
|
|
439
583
|
writeText(path.join(wikiRoot, directory, assetOutputFileName(directory, id)),
|
|
440
|
-
ensureReadableHeading(mergePage(source, brief, authorityFrontmatter(type, id, title, authority),
|
|
584
|
+
ensureReadableHeading(mergePage(source, brief, authorityFrontmatter(type, id, title, authority), mergedExtra), title));
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function renderAssetAuthorityNotice(authority) {
|
|
588
|
+
const runtimePolicy = clean(authority.runtime_policy);
|
|
589
|
+
const definitionState = clean(authority.definition_state);
|
|
590
|
+
const authorityRole = clean(authority.authority_role);
|
|
591
|
+
const canonical = clean(authority.canonical_asset_id);
|
|
592
|
+
const lines = [];
|
|
593
|
+
if (runtimePolicy === 'block') {
|
|
594
|
+
lines.push('- 召回限制:该资产不能作为直接回答入口。');
|
|
595
|
+
} else if (runtimePolicy === 'warn') {
|
|
596
|
+
lines.push('- 召回限制:该资产只能作为辅助线索,回答时需要说明其依赖或待确认边界。');
|
|
597
|
+
}
|
|
598
|
+
if (authorityRole === 'dependency_only') {
|
|
599
|
+
lines.push('- 权威边界:该资产来自依赖闭包,只能支撑承载看板或业务域,不能单独代表正式业务入口。');
|
|
600
|
+
}
|
|
601
|
+
if (definitionState === 'conflict') {
|
|
602
|
+
lines.push('- 口径状态:存在同名或近义资产口径冲突,应优先回到业务域、召回卡或让用户确认。');
|
|
603
|
+
} else if (definitionState === 'unknown') {
|
|
604
|
+
lines.push('- 口径状态:定义证据不足,只能作为待核验线索。');
|
|
605
|
+
}
|
|
606
|
+
if (canonical) {
|
|
607
|
+
lines.push(`- 推荐入口:优先使用 \`${canonical}\`,本页只保留来源和排除理由。`);
|
|
608
|
+
}
|
|
609
|
+
if (!lines.length) return '';
|
|
610
|
+
return `## 召回与权威边界\n\n${lines.join('\n')}`;
|
|
441
611
|
}
|
|
442
612
|
|
|
443
613
|
function mergePage(source, brief, frontmatter, extra = '') {
|
|
@@ -525,7 +695,7 @@ function recallAssetLabel(ref) {
|
|
|
525
695
|
}
|
|
526
696
|
|
|
527
697
|
function renderRecallCardIndex(cards) {
|
|
528
|
-
return `# 场景召回卡\n\n首轮召回先使用这里的业务问题与权威资产路由,不直接宽搜全部资产页。\n\n${cards.map((card) => `- [${escapeLink(card.
|
|
698
|
+
return `# 场景召回卡\n\n首轮召回先使用这里的业务问题与权威资产路由,不直接宽搜全部资产页。\n\n${cards.map((card) => `- [${escapeLink(card.intent || card.card_id)}](${fileName('recall-card', card.card_id)})(${clean(card.domain_title)})`).join('\n')}\n`;
|
|
529
699
|
}
|
|
530
700
|
|
|
531
701
|
function renderAuthorityIndex(rows) {
|
|
@@ -555,16 +725,48 @@ function renderGovernanceIndex(conflicts) {
|
|
|
555
725
|
}
|
|
556
726
|
|
|
557
727
|
function renderWikiIndex(corpus) {
|
|
558
|
-
return `---\ntype: project-semantic-knowledge-wiki-index\nsemantic_project_id: ${semanticProjectId}\nproject_name: ${yaml(projectName)}\nsource_snapshot_hash: ${yaml(snapshotHash)}\n---\n\n# ${projectName} 项目语义知识库 Wiki\n\n## 查询顺序\n\n1. [场景召回卡](recall-cards/index.md)\
|
|
728
|
+
return `---\ntype: project-semantic-knowledge-wiki-index\nsemantic_project_id: ${semanticProjectId}\nproject_name: ${yaml(projectName)}\nsource_snapshot_hash: ${yaml(snapshotHash)}\n---\n\n# ${projectName} 项目语义知识库 Wiki\n\n## 查询顺序\n\n1. [项目业务模型](project-overview.md)\n2. [场景召回卡](recall-cards/index.md)\n3. [业务域](domains/index.md)\n4. [看板](dashboards/index.md)、[报表](reports/index.md)、[可复用指标](metrics/index.md)\n5. [分析元数据](metadata/index.md)\n6. [治理与质量](governance/index.md)\n\n技术、演示、副本和定向诊断资产只在[技术附录](technical-appendices/index.md)中追溯。物理空间在[source containers](source-containers/index.md)中保留,不作为业务域。\n\n${countSummary(corpus)}\n`;
|
|
559
729
|
}
|
|
560
730
|
|
|
561
731
|
function renderSnapshotIndex(corpus) {
|
|
562
732
|
const title = String(projectName).replace(/\r?\n/g, ' ').replace(/^#+\s*/, '');
|
|
563
|
-
return `# ${title} 项目语义知识库 Wiki\n\n这是可导入知识库源文件体系的项目语义 Wiki,共包含 ${corpus.counts.dashboards} 张看板、${corpus.counts.reports} 张报表和 ${corpus.counts.analysis_metadata} 条分析元数据。\n\n## 查询顺序\n\n1. [场景召回卡](wiki/recall-cards/index.md)\
|
|
733
|
+
return `# ${title} 项目语义知识库 Wiki\n\n这是可导入知识库源文件体系的项目语义 Wiki,共包含 ${corpus.counts.dashboards} 张看板、${corpus.counts.reports} 张报表和 ${corpus.counts.analysis_metadata} 条分析元数据。\n\n## 查询顺序\n\n1. [项目业务模型](wiki/project-overview.md)\n2. [场景召回卡](wiki/recall-cards/index.md)\n3. [业务域](wiki/domains/index.md)\n4. [看板](wiki/dashboards/index.md)、[报表](wiki/reports/index.md)、[可复用指标](wiki/metrics/index.md)\n5. [分析元数据](wiki/metadata/index.md)\n6. [治理与质量](wiki/governance/index.md)\n\n技术、演示、副本和定向诊断资产只在[技术附录](wiki/technical-appendices/index.md)中追溯。物理空间在[source containers](wiki/source-containers/index.md)中保留,不作为业务域。\n\n${countSummary(corpus)}\n`;
|
|
564
734
|
}
|
|
565
735
|
|
|
566
736
|
function renderProjectOverview(corpus) {
|
|
567
|
-
return `# ${projectName}
|
|
737
|
+
return `# ${projectName} 项目业务模型\n\n${renderProjectBusinessModel(planModel.projectBusinessModel)}\n\n## 资产与治理范围\n\n- Semantic project ID: ${semanticProjectId}\n- Snapshot hash: \`${snapshotHash}\`\n- Asset scope: ${packageAssetScope}\n- Business domains: ${corpus.counts.business_domains}\n- Dashboards: ${corpus.counts.dashboards}\n- Reports: ${corpus.counts.reports}\n- Reusable metrics: ${corpus.counts.reusable_metrics}\n- Metadata: ${corpus.counts.analysis_metadata}\n\nCommon 资产包是权威来源。本 Wiki 将业务判断模型、用途总结和精确证据合并为单一页面,不再发布第二套 raw/briefs 语料。\n`;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function renderProjectBusinessModel(model) {
|
|
741
|
+
return `## 项目定位\n\n${clean(model.business_positioning)}\n\n## 使用角色\n\n${asArray(model.audience_roles).map((role) => `- ${clean(role)}`).join('\n')}\n\n## 业务对象\n\n${asArray(model.business_objects).map((item) => {
|
|
742
|
+
const evidence = asArray(item.evidence_refs).map(renderOverviewAssetRef).join('、');
|
|
743
|
+
return `- ${clean(item.name)}(\`${clean(item.object_id)}\`):${appendEvidenceSentence(item.meaning, evidence)}`;
|
|
744
|
+
}).join('\n')}\n\n## 对象关系\n\n${asArray(model.object_relationships).map((item) => {
|
|
745
|
+
const evidence = asArray(item.evidence_refs).map(renderOverviewAssetRef).join('、');
|
|
746
|
+
return `- ${clean(item.from_object)} -> ${clean(item.to_object)}:${appendEvidenceSentence(item.relationship, evidence)}`;
|
|
747
|
+
}).join('\n')}\n\n## 业务判断链路\n\n${asArray(model.decision_chains).map(renderDecisionChain).join('\n\n')}\n\n## 不承载的内容\n\n${asArray(model.non_goals).map((item) => `- ${clean(item)}`).join('\n')}`;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function appendEvidenceSentence(value, evidence) {
|
|
751
|
+
const body = clean(value).replace(/[。;;,,\s]+$/u, '');
|
|
752
|
+
return `${body}${evidence ? `。证据:${evidence}` : ''}`;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function renderDecisionChain(chain) {
|
|
756
|
+
const domains = asArray(chain.preferred_domain_ids)
|
|
757
|
+
.map((id) => `[\`${clean(id)}\`](domains/${fileName('domain', id)})`).join('、');
|
|
758
|
+
const assets = asArray(chain.asset_refs).map(renderOverviewAssetRef).join('、');
|
|
759
|
+
return `### ${clean(chain.title)}\n\n- 判断问题:${stripClausePunctuation(chain.decision_question)}\n- 判断信号:${joinChineseClauses(chain.signals)}\n- 判断步骤:\n${renderNumberedSteps(chain.decision_steps, ' ')}\n- 业务域:${domains || '未指定'}\n- 关联资产:${assets || '未指定'}\n- 实时执行:${chain.requires_live_execution ? '需要' : '不需要'}\n- 边界:${joinChineseClauses(chain.boundaries)}`;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function renderOverviewAssetRef(ref) {
|
|
763
|
+
const type = clean(ref.resource_type);
|
|
764
|
+
const key = clean(ref.resource_key);
|
|
765
|
+
const label = recallAssetLabel(ref);
|
|
766
|
+
if (type === 'dashboard') return `[${escapeLink(label)}](dashboards/${dashboardFileNames.get(key) ?? fileName('dashboard', key)})`;
|
|
767
|
+
if (type === 'report') return `[${escapeLink(label)}](reports/${reportFileNames.get(key) ?? fileName('report', key)})`;
|
|
768
|
+
if (type === 'metric') return `[${escapeLink(label)}](metrics/${metricFileNames.get(key) ?? fileName('metric', key)})`;
|
|
769
|
+
return `${type}:${key}`;
|
|
568
770
|
}
|
|
569
771
|
|
|
570
772
|
function renderLog(corpus) {
|
|
@@ -744,6 +946,7 @@ function hideMarkdownFrontmatter(root) {
|
|
|
744
946
|
function assertSnapshotEntryPoint(root) {
|
|
745
947
|
const content = readText(path.join(root, 'index.md'));
|
|
746
948
|
const requiredLinks = [
|
|
949
|
+
'wiki/project-overview.md',
|
|
747
950
|
'wiki/recall-cards/index.md', 'wiki/domains/index.md', 'wiki/dashboards/index.md',
|
|
748
951
|
'wiki/reports/index.md', 'wiki/metrics/index.md', 'wiki/metadata/index.md',
|
|
749
952
|
'wiki/governance/index.md', 'wiki/technical-appendices/index.md',
|
|
@@ -876,6 +1079,7 @@ function writeFlatUploadSources(sourceRoot, targetRoot, replace) {
|
|
|
876
1079
|
const content = precompiledSource(rewriteLinksForFlatUpload(readText(file), rel, relToFlat));
|
|
877
1080
|
const contentHash = syncContentHash(content);
|
|
878
1081
|
const identity = uploadSourceIdentity(rel, content);
|
|
1082
|
+
const semanticPlanFragmentRefs = semanticPlanFragmentRefsForSource(identity, rel);
|
|
879
1083
|
manifestSources.push({
|
|
880
1084
|
display_name: flatName,
|
|
881
1085
|
file: flatName,
|
|
@@ -888,8 +1092,9 @@ function writeFlatUploadSources(sourceRoot, targetRoot, replace) {
|
|
|
888
1092
|
title: firstHeading(content),
|
|
889
1093
|
content_hash: contentHash,
|
|
890
1094
|
source_path: rel,
|
|
1095
|
+
semantic_plan_fragment_refs: semanticPlanFragmentRefs,
|
|
891
1096
|
});
|
|
892
|
-
writeText(path.join(staging, flatName), `<!--\nkb_source_namespace: ${uploadSourceNamespace}\nsnapshot_date: ${snapshotDate ?? 'null'}\nsnapshot_hash: ${snapshotHash}\nsource_kind: ${identity.source_kind}\nasset_type: ${identity.asset_type ?? 'null'}\nasset_key: ${identity.asset_key ?? 'null'}\ncontent_hash: ${contentHash}\nsource_path: ${rel}\n-->\n\n${content}`);
|
|
1097
|
+
writeText(path.join(staging, flatName), `<!--\nkb_source_namespace: ${uploadSourceNamespace}\nsnapshot_date: ${snapshotDate ?? 'null'}\nsnapshot_hash: ${snapshotHash}\nsource_kind: ${identity.source_kind}\nasset_type: ${identity.asset_type ?? 'null'}\nasset_key: ${identity.asset_key ?? 'null'}\ncontent_hash: ${contentHash}\nsource_path: ${rel}\nsemantic_plan_fragment_refs: ${semanticPlanFragmentRefs.join(',') || 'null'}\n-->\n\n${content}`);
|
|
893
1098
|
}
|
|
894
1099
|
const manifest = readJson(path.join(sourceRoot, 'manifest.json'));
|
|
895
1100
|
const manifestContent = precompiledSource(`# Package manifest\n\n\`\`\`json\n${JSON.stringify(manifest, null, 2)}\n\`\`\`\n`);
|
|
@@ -907,8 +1112,9 @@ function writeFlatUploadSources(sourceRoot, targetRoot, replace) {
|
|
|
907
1112
|
title: 'Package manifest',
|
|
908
1113
|
content_hash: manifestHash,
|
|
909
1114
|
source_path: 'manifest.json',
|
|
1115
|
+
semantic_plan_fragment_refs: [],
|
|
910
1116
|
});
|
|
911
|
-
writeText(path.join(staging, manifestName), `<!--\nkb_source_namespace: ${uploadSourceNamespace}\nsnapshot_date: ${snapshotDate ?? 'null'}\nsnapshot_hash: ${snapshotHash}\nsource_kind: manifest\nasset_type: null\nasset_key: null\ncontent_hash: ${manifestHash}\nsource_path: manifest.json\n-->\n\n${manifestContent}`);
|
|
1117
|
+
writeText(path.join(staging, manifestName), `<!--\nkb_source_namespace: ${uploadSourceNamespace}\nsnapshot_date: ${snapshotDate ?? 'null'}\nsnapshot_hash: ${snapshotHash}\nsource_kind: manifest\nasset_type: null\nasset_key: null\ncontent_hash: ${manifestHash}\nsource_path: manifest.json\nsemantic_plan_fragment_refs: null\n-->\n\n${manifestContent}`);
|
|
912
1118
|
writeJson(path.join(staging, 'kb-upload-manifest.json'), {
|
|
913
1119
|
schema_version: '1.0',
|
|
914
1120
|
manifest_kind: 'ae_project_semantic_kb_upload_sources',
|
|
@@ -917,6 +1123,7 @@ function writeFlatUploadSources(sourceRoot, targetRoot, replace) {
|
|
|
917
1123
|
project_name: projectName,
|
|
918
1124
|
snapshot_date: snapshotDate,
|
|
919
1125
|
snapshot_hash: snapshotHash,
|
|
1126
|
+
semantic_plan_state: semanticPlanState,
|
|
920
1127
|
source_count: manifestSources.length,
|
|
921
1128
|
sources: manifestSources.sort((a, b) => a.display_name.localeCompare(b.display_name, 'en')),
|
|
922
1129
|
});
|
|
@@ -940,6 +1147,7 @@ function flatUploadFileName(rel, namespace) {
|
|
|
940
1147
|
function uploadSourceIdentity(rel, content = '') {
|
|
941
1148
|
const normalized = rel.replaceAll('\\', '/');
|
|
942
1149
|
if (normalized === 'index.md') return { source_kind: 'index', asset_type: null, asset_key: null, file_stem: 'index' };
|
|
1150
|
+
if (normalized === 'refresh-state.md') return { source_kind: 'refresh-state', asset_type: null, asset_key: null, file_stem: 'refresh-state' };
|
|
943
1151
|
if (normalized === 'wiki/index.md') return { source_kind: 'index', asset_type: null, asset_key: null, file_stem: 'wiki-index' };
|
|
944
1152
|
const match = normalized.match(/^wiki\/([^/]+)\/(.+)\.md$/);
|
|
945
1153
|
if (!match) {
|
|
@@ -966,6 +1174,108 @@ function uploadSourceIdentity(rel, content = '') {
|
|
|
966
1174
|
return namedUploadSource(section, stem);
|
|
967
1175
|
}
|
|
968
1176
|
|
|
1177
|
+
function semanticPlanFragmentRefsForSource(identity, rel) {
|
|
1178
|
+
const refs = new Set();
|
|
1179
|
+
const sourceKind = clean(identity.source_kind);
|
|
1180
|
+
const assetType = clean(identity.asset_type);
|
|
1181
|
+
const assetKey = clean(identity.asset_key);
|
|
1182
|
+
if (sourceKind === 'refresh-state') {
|
|
1183
|
+
for (const fragment of semanticPlanState.fragments) refs.add(fragment.id);
|
|
1184
|
+
return [...refs].sort();
|
|
1185
|
+
}
|
|
1186
|
+
if (rel === 'wiki/project-overview.md') refs.add('project:model');
|
|
1187
|
+
if (sourceKind === 'domain' && assetKey) refs.add(`domain:${assetKey}`);
|
|
1188
|
+
if (sourceKind === 'appendix' && assetKey) refs.add(`appendix:${assetKey}`);
|
|
1189
|
+
if (sourceKind === 'recall-card' && assetKey) refs.add(`recall-card:${assetKey}`);
|
|
1190
|
+
if (assetType === 'dashboard' && assetKey) {
|
|
1191
|
+
const domain = domainByDashboardId.get(assetKey);
|
|
1192
|
+
const appendix = appendixByDashboardId.get(assetKey);
|
|
1193
|
+
if (domain) refs.add(`domain:${clean(domain.domain_id)}`);
|
|
1194
|
+
if (appendix) refs.add(`appendix:${clean(appendix.appendix_id)}`);
|
|
1195
|
+
}
|
|
1196
|
+
if (assetType === 'report' && assetKey) {
|
|
1197
|
+
if (planModel.sqlByReportId.has(assetKey)) refs.add(`report:${assetKey}:sql_semantics`);
|
|
1198
|
+
const report = reportById.get(assetKey);
|
|
1199
|
+
for (const dashboardRef of asArray(report?.dashboard_refs)) {
|
|
1200
|
+
const dashboardId = clean(dashboardRef.dashboard_id);
|
|
1201
|
+
const domain = domainByDashboardId.get(dashboardId);
|
|
1202
|
+
const appendix = appendixByDashboardId.get(dashboardId);
|
|
1203
|
+
if (domain) refs.add(`domain:${clean(domain.domain_id)}`);
|
|
1204
|
+
if (appendix) refs.add(`appendix:${clean(appendix.appendix_id)}`);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
if (sourceKind === 'index' && rel === 'wiki/recall-cards/index.md') {
|
|
1208
|
+
for (const card of planModel.cards) refs.add(`recall-card:${clean(card.card_id)}`);
|
|
1209
|
+
}
|
|
1210
|
+
if (sourceKind === 'index' && rel === 'wiki/domains/index.md') {
|
|
1211
|
+
for (const domain of domains) refs.add(`domain:${clean(domain.domain_id)}`);
|
|
1212
|
+
}
|
|
1213
|
+
return [...refs].filter(Boolean).sort();
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
function buildSemanticPlanState() {
|
|
1217
|
+
const fragments = [];
|
|
1218
|
+
const add = (id, kind, value, fields = {}) => {
|
|
1219
|
+
const fragmentId = clean(id);
|
|
1220
|
+
if (!fragmentId) return;
|
|
1221
|
+
fragments.push({
|
|
1222
|
+
id: fragmentId,
|
|
1223
|
+
kind,
|
|
1224
|
+
...fields,
|
|
1225
|
+
hash: sha256(stableJson(value)),
|
|
1226
|
+
});
|
|
1227
|
+
};
|
|
1228
|
+
add('project:model', 'project', semanticPlan.project_business_model ?? null);
|
|
1229
|
+
for (const domain of domains) {
|
|
1230
|
+
const id = clean(domain.domain_id);
|
|
1231
|
+
const { recall_cards: _recallCards, ...domainWithoutCards } = asObject(domain);
|
|
1232
|
+
add(`domain:${id}`, 'domain', domainWithoutCards, { domain_id: id });
|
|
1233
|
+
for (const card of asArray(domain.recall_cards)) {
|
|
1234
|
+
const cardId = clean(card.card_id);
|
|
1235
|
+
add(`recall-card:${cardId}`, 'recall-card', { ...asObject(card), domain_id: id }, { card_id: cardId, domain_id: id });
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
for (const appendix of appendices) {
|
|
1239
|
+
const id = clean(appendix.appendix_id);
|
|
1240
|
+
add(`appendix:${id}`, 'appendix', appendix, { appendix_id: id });
|
|
1241
|
+
}
|
|
1242
|
+
for (const [reportId, summary] of [...planModel.sqlByReportId.entries()].sort(([left], [right]) => left.localeCompare(right, 'en'))) {
|
|
1243
|
+
add(`report:${reportId}:sql_semantics`, 'sql-report', summary, { report_id: reportId });
|
|
1244
|
+
}
|
|
1245
|
+
fragments.sort((left, right) => left.id.localeCompare(right.id, 'en'));
|
|
1246
|
+
return {
|
|
1247
|
+
schema_version: '1.0',
|
|
1248
|
+
plan_schema_version: clean(semanticPlan.schema_version),
|
|
1249
|
+
plan_hash: sha256(stableJson(semanticPlan)),
|
|
1250
|
+
fragment_count: fragments.length,
|
|
1251
|
+
fragments,
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
function renderRefreshState(state) {
|
|
1256
|
+
const payload = {
|
|
1257
|
+
schema_version: '1.0',
|
|
1258
|
+
semantic_plan: semanticPlan,
|
|
1259
|
+
semantic_plan_state: state,
|
|
1260
|
+
};
|
|
1261
|
+
const encoded = Buffer.from(JSON.stringify(payload, null, 2)).toString('base64url');
|
|
1262
|
+
return `# Refresh State
|
|
1263
|
+
|
|
1264
|
+
This managed source stores the reusable semantic plan and incremental-refresh state for generated project semantic sources. It is not business evidence.
|
|
1265
|
+
|
|
1266
|
+
<!-- pskb_refresh_state_base64
|
|
1267
|
+
${encoded}
|
|
1268
|
+
-->
|
|
1269
|
+
`;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
function stableJson(value) {
|
|
1273
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(',')}]`;
|
|
1274
|
+
if (!value || typeof value !== 'object') return JSON.stringify(value);
|
|
1275
|
+
const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right, 'en'));
|
|
1276
|
+
return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(',')}}`;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
969
1279
|
function namedUploadSource(kind, stem, resourceKey = '') {
|
|
970
1280
|
const key = stem === 'index' ? null : clean(resourceKey) || stem;
|
|
971
1281
|
return {
|
|
@@ -1027,11 +1337,26 @@ function asObject(value) { return value && typeof value === 'object' && !Array.i
|
|
|
1027
1337
|
function indexBy(values, key) { return new Map(values.map((value) => [clean(value[key]), value])); }
|
|
1028
1338
|
function readText(file) { return fs.readFileSync(file, 'utf8'); }
|
|
1029
1339
|
function readJson(file) { return JSON.parse(readText(file)); }
|
|
1340
|
+
|
|
1341
|
+
function stripClausePunctuation(value) {
|
|
1342
|
+
return clean(value).replace(/[。;;,,\s]+$/u, '');
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
function joinChineseClauses(value) {
|
|
1346
|
+
const values = asArray(value).map(stripClausePunctuation).filter(Boolean);
|
|
1347
|
+
return values.join(';') || '无';
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
function renderNumberedSteps(value, indent = '') {
|
|
1351
|
+
const values = asArray(value).map(stripClausePunctuation).filter(Boolean);
|
|
1352
|
+
if (!values.length) return `${indent}- 无`;
|
|
1353
|
+
return values.map((item, index) => `${indent}${index + 1}. ${item}`).join('\n');
|
|
1354
|
+
}
|
|
1030
1355
|
function writeText(file, value) { fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, value.endsWith('\n') ? value : `${value}\n`, 'utf8'); }
|
|
1031
1356
|
function writeJson(file, value) { writeText(file, JSON.stringify(value, null, 2)); }
|
|
1032
1357
|
function readIndex(name) { return readText(path.join(packageRoot, 'indexes', `${name}.jsonl`)).split(/\r?\n/).filter(Boolean).map(JSON.parse).filter((row) => row.record_type !== 'header'); }
|
|
1033
1358
|
function listFiles(root) { return fs.readdirSync(root, { withFileTypes: true }).flatMap((entry) => entry.isDirectory() ? listFiles(path.join(root, entry.name)) : [path.join(root, entry.name)]); }
|
|
1034
|
-
function parseArgs(values) { const out = {}; for (let i = 0; i < values.length; i += 1) { const value = values[i]; if (!value.startsWith('--')) fail(`Unexpected argument: ${value}`); const key = value.slice(2); if (['force', 'keep-build-ir', 'allow-all-visible'].includes(key)) out[key] = true; else out[key] = values[++i]; } return out; }
|
|
1359
|
+
function parseArgs(values) { const out = {}; for (let i = 0; i < values.length; i += 1) { const value = values[i]; if (!value.startsWith('--')) fail(`Unexpected argument: ${value}`); const key = value.slice(2); if (['force', 'keep-build-ir', 'allow-all-visible', 'allow-semantic-plan-snapshot-drift'].includes(key)) out[key] = true; else out[key] = values[++i]; } return out; }
|
|
1035
1360
|
function required(values, key) { const value = clean(values[key]); if (!value) fail(`--${key} is required.`); return value; }
|
|
1036
1361
|
function normalizeUploadSourceNamespace(value) {
|
|
1037
1362
|
const namespace = slug(clean(value));
|
|
@@ -7,6 +7,7 @@ These rules guide Schema generation for project semantic knowledge bases built f
|
|
|
7
7
|
- Treat `sources/README.md` and category `README.md` files as filing guides. They define where future material belongs; they are not business evidence.
|
|
8
8
|
- CLI-managed asset facts live under `sources/assets/**`. Human project background, business rules, meetings, decisions, corrections, and references live outside `sources/assets/**`; asset refresh must not delete or override those human inputs.
|
|
9
9
|
- Organize generated Wiki pages for knowledge consumption, not by mirroring every source file. Use project overview, business domains, reusable semantics, assets, decisions, and review pages.
|
|
10
|
+
- The project overview must start with the project-level business model: business positioning, audience roles, business objects, object relationships, decision chains, asset roles in those chains, and non-goals. Asset counts, snapshot hashes, source package metadata and build logs belong after that model. Do not reduce the overview to a directory, asset inventory, or build summary.
|
|
10
11
|
- Do not infer business meaning from titles alone. Preserve unknowns, conflicts, source scope, certification state, and provenance when evidence is incomplete.
|
|
11
12
|
- Corrections and decisions override other material only when they state target, content, date, status, and applicable scope.
|
|
12
13
|
|
|
@@ -21,10 +22,14 @@ When linking to an individual asset, use its business title as the visible link
|
|
|
21
22
|
|
|
22
23
|
CLI Agent has already performed the main semantic compilation for ALL assets: business meanings, SQL interpretation, calculation definitions, relationships, domains, recall boundaries, and conflict findings. These are editable source documents, not an imported final Wiki snapshot. Reuse their complete prose and calculation facts. Do not repeat project-wide extraction, SQL parsing, semantic planning, or per-field rewriting.
|
|
23
24
|
|
|
24
|
-
Business-domain pages include an `Agent 使用摘要` for first-hop routing. Preserve
|
|
25
|
+
Business-domain pages include a judgment model and an `Agent 使用摘要` for first-hop routing. Preserve them as the concise answer source for "what business state does this domain judge", "which objects/signals/decision path should the Agent follow", "which assets are primary/supporting/drilldown/excluded", "what does this domain solve", "which events/metrics are mainly used", and "which dimensions should the agent drill down by". Do not replace them with a raw report list or regenerate them from every report page unless newer scoped evidence explicitly changes the summary.
|
|
26
|
+
|
|
27
|
+
Reject or repair any generated Wiki shape where the root, overview, or business-domain pages only list questions, events, metrics, dimensions, reports, dashboards or counts without explaining the business judgment chain that makes those assets useful. Examples in source instructions are illustrative only; never hardcode project-specific business domains, assets, metrics, event names or decision chains into the compiled Wiki.
|
|
25
28
|
|
|
26
29
|
KB must compile and publish the final Wiki: preserve prepared asset sections, map source references to Wiki links, build concise titled navigation, and integrate project background, meetings, decisions and corrections. Batch independent page writes and link checks. Do not read each source repeatedly or regenerate unchanged pages.
|
|
27
30
|
|
|
28
|
-
For SQL assets, preserve the complete Agent-authored purpose, parameters, output fields, grain, filters, limits, applicable/non-applicable questions and evidence locator in the report page's single `Agent 使用摘要`. Raw SQL/index JSON stays in the local evidence package; do not attempt to fetch it or infer omitted details. Missing facts remain unknown. Preserve certification, dependency-only status, conflicts and blocked recall; do not dismiss safety states as stale placeholders.
|
|
31
|
+
For SQL assets, preserve the complete Agent-authored purpose, parameters, output fields, grain, filters, limits, applicable/non-applicable questions and evidence locator in the report page's single `Agent 使用摘要`. Raw SQL/index JSON stays in the local evidence package; do not attempt to fetch it or infer omitted details. Missing facts remain unknown. Preserve certification, dependency-only status, conflicts and blocked recall; do not dismiss safety states as stale placeholders. Keep build-process markers out of visible Wiki prose: do not expose `CLI Agent`, `摘要来源`, `semantic_plan`, parser fallback labels, or builder/debug terminology as user-facing business content.
|
|
32
|
+
|
|
33
|
+
For non-SQL report pages, preserve the `Agent 使用摘要` as a usable business routing summary, not a mechanical parser echo. The purpose and applicable-question bullets must not be identical. Metric, dimension and filter sections should preserve structured evidence when present and use source-confirmation wording for unavailable facts. Do not introduce broad placeholder phrases such as `未识别到稳定下钻维度` or `未发现明确过滤` when a safer "open the source report to confirm" boundary is already present.
|
|
29
34
|
|
|
30
35
|
During incremental compilation, inspect changed sources and their existing dependent pages. Update affected asset pages, indexes and cross-source conclusions; retain unrelated pages. Remove obsolete current assertions when a source changes or is removed. Integrate human evidence according to explicit scope, status and authority, never recency alone. Scope differences belong separately from same-scope contradictions. Preserve provenance to the actual uploaded source.
|