scene-capability-engine 3.3.24 → 3.3.26

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/CHANGELOG.md CHANGED
@@ -14,6 +14,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
14
14
  - New script gate:
15
15
  - `node scripts/errorbook-registry-health-gate.js`
16
16
  - supports strict mode via `SCE_REGISTRY_HEALTH_STRICT=1`
17
+ - Local project timeline snapshot system:
18
+ - new command group: `sce timeline ...`
19
+ - supports manual save/list/show/restore/config and `timeline push` (pre-push checkpoint + git push)
20
+ - snapshots are retained under `.sce/timeline/snapshots/` with configurable retention policy
21
+ - key-stage checkpoint integration for `studio` and `session` command flows
17
22
 
18
23
  ### Changed
19
24
  - `prepublishOnly` now runs `gate:errorbook-registry-health` in advisory mode before `errorbook-release` gate.
@@ -42,6 +47,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
42
47
  - when `--spec` is omitted, `plan` auto-selects the latest scene-matching chain (`scene_id`)
43
48
  - `sce studio generate` writes chain-aware metadata and `generate` stage report at `.sce/reports/studio/generate-<job-id>.json`
44
49
  - `sce studio verify` / `sce studio release` now include domain-chain metadata in reports and pass `spec_id` into auto errorbook failure capture
50
+ - Added historical related-spec retrieval for faster new-problem analysis:
51
+ - new command: `sce spec-related` (alias route: `sce spec related`)
52
+ - supports query/scene/spec-seeded lookup and relevance ranking
53
+ - `sce studio plan` now auto-loads related historical specs into job metadata (`source.related_specs`)
54
+ - SCE now captures timeline checkpoints by default on `studio`/`session` key operations, and performs interval auto-check in the same checkpoint pipeline to reduce local history-loss risk.
45
55
 
46
56
  ## [3.3.23] - 2026-02-27
47
57
 
@@ -20,6 +20,8 @@ const { registerSpecBootstrapCommand } = require('../lib/commands/spec-bootstrap
20
20
  const { registerSpecPipelineCommand } = require('../lib/commands/spec-pipeline');
21
21
  const { registerSpecGateCommand } = require('../lib/commands/spec-gate');
22
22
  const { registerSpecDomainCommand } = require('../lib/commands/spec-domain');
23
+ const { registerSpecRelatedCommand } = require('../lib/commands/spec-related');
24
+ const { registerTimelineCommands } = require('../lib/commands/timeline');
23
25
  const { registerValueCommands } = require('../lib/commands/value');
24
26
  const VersionChecker = require('../lib/version/version-checker');
25
27
  const {
@@ -57,6 +59,7 @@ const program = new Command();
57
59
  * - `sce spec pipeline ...` -> `sce spec-pipeline ...`
58
60
  * - `sce spec gate ...` -> `sce spec-gate ...`
59
61
  * - `sce spec domain ...` -> `sce spec-domain ...`
62
+ * - `sce spec related ...` -> `sce spec-related ...`
60
63
  * - `sce spec create <name> ...` -> `sce create-spec <name> ...`
61
64
  * - `sce spec <name> ...` -> `sce create-spec <name> ...` (legacy)
62
65
  *
@@ -96,6 +99,11 @@ function normalizeSpecCommandArgs(argv) {
96
99
  return normalized;
97
100
  }
98
101
 
102
+ if (commandToken === 'related') {
103
+ normalized.splice(commandIndex, 2, 'spec-related');
104
+ return normalized;
105
+ }
106
+
99
107
  if (commandToken === 'create') {
100
108
  normalized.splice(commandIndex, 2, 'create-spec');
101
109
  return normalized;
@@ -342,6 +350,9 @@ registerSpecGateCommand(program);
342
350
  // Spec domain modeling command
343
351
  registerSpecDomainCommand(program);
344
352
 
353
+ // Spec related lookup command
354
+ registerSpecRelatedCommand(program);
355
+
345
356
  // 系统诊断命令
346
357
  program
347
358
  .command('doctor')
@@ -810,6 +821,7 @@ registerCollabCommands(program);
810
821
  // Universal steering and runtime session commands
811
822
  registerSteeringCommands(program);
812
823
  registerSessionCommands(program);
824
+ registerTimelineCommands(program);
813
825
 
814
826
  // Autonomous control commands
815
827
  const { registerAutoCommands } = require('../lib/commands/auto');
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Quick reference for all `sce` commands
4
4
 
5
- **Version**: 3.3.23
5
+ **Version**: 3.3.25
6
6
  **Last Updated**: 2026-02-27
7
7
 
8
8
  ---
@@ -73,6 +73,10 @@ sce spec domain init --spec 01-00-feature-name --scene scene.customer-order-inve
73
73
  sce spec domain validate --spec 01-00-feature-name --fail-on-error --json
74
74
  sce spec domain refresh --spec 01-00-feature-name --scene scene.customer-order-inventory --json
75
75
 
76
+ # Find related historical specs before starting a new analysis
77
+ sce spec related --query "customer order inventory reconciliation drift" --scene scene.customer-order-inventory --json
78
+ sce spec related --spec 01-00-feature-name --limit 8 --json
79
+
76
80
  # Multi-Spec mode defaults to orchestrate routing
77
81
  sce spec bootstrap --specs "spec-a,spec-b" --max-parallel 3
78
82
  sce spec pipeline run --specs "spec-a,spec-b" --max-parallel 3
@@ -92,6 +96,37 @@ Spec session governance:
92
96
  - `.sce/specs/<spec>/custom/scene-spec.md`
93
97
  - `.sce/specs/<spec>/custom/problem-domain-chain.json`
94
98
 
99
+ ### Timeline Snapshots
100
+
101
+ ```bash
102
+ # Manual checkpoint
103
+ sce timeline save --summary "before large refactor" --json
104
+
105
+ # Auto interval checkpoint tick (skips when interval is not reached)
106
+ sce timeline auto --json
107
+
108
+ # List and inspect snapshots
109
+ sce timeline list --limit 20 --json
110
+ sce timeline show <snapshot-id> --json
111
+
112
+ # Restore workspace to a snapshot (safe mode keeps extra files)
113
+ sce timeline restore <snapshot-id> --json
114
+
115
+ # Hard restore (also prune files not in snapshot)
116
+ sce timeline restore <snapshot-id> --prune --json
117
+
118
+ # Update timeline policy
119
+ sce timeline config --enabled true --interval 30 --max-entries 120 --json
120
+
121
+ # Push with pre-push snapshot
122
+ sce timeline push origin main
123
+ ```
124
+
125
+ Timeline policy:
126
+ - default enabled with local retention under `.sce/timeline/snapshots/`
127
+ - stage/key-event checkpoints are automatically captured for `studio` and `session` commands
128
+ - interval auto-checkpoints are integrated in the same flow via timeline checkpoint capture
129
+
95
130
  ### Value Metrics
96
131
 
97
132
  ```bash
@@ -491,6 +526,7 @@ Stage guardrails are enforced by default:
491
526
  - `plan` requires `--scene`; SCE binds one active primary session per scene
492
527
  - `plan --spec <id>` (recommended) ingests `.sce/specs/<spec>/custom/problem-domain-chain.json` into studio job context
493
528
  - when `--spec` is omitted, `plan` auto-resolves the latest matching spec chain by `scene_id` when available
529
+ - `plan` auto-searches related historical specs by `scene + goal` and writes top candidates into job metadata (`source.related_specs`)
494
530
  - successful `release` auto-archives current scene session and auto-opens the next scene cycle session
495
531
  - `generate` requires `plan`
496
532
  - `generate` consumes the plan-stage domain-chain context and writes chain-aware metadata/report (`.sce/reports/studio/generate-<job-id>.json`)
@@ -1,5 +1,6 @@
1
1
  const chalk = require('chalk');
2
2
  const { SessionStore } = require('../runtime/session-store');
3
+ const { captureTimelineCheckpoint } = require('../runtime/project-timeline');
3
4
 
4
5
  function registerSessionCommands(program) {
5
6
  const session = program
@@ -15,6 +16,14 @@ function registerSessionCommands(program) {
15
16
  .option('--json', 'Output as JSON')
16
17
  .action(async (objective, options) => {
17
18
  try {
19
+ await captureTimelineCheckpoint({
20
+ trigger: 'key-event',
21
+ event: 'session.start',
22
+ summary: `session start | tool=${options.tool || 'generic'} | objective=${objective || ''}`.trim(),
23
+ command: 'sce session start'
24
+ }, {
25
+ projectPath: process.cwd()
26
+ });
18
27
  const store = new SessionStore(process.cwd());
19
28
  const created = await store.startSession({
20
29
  tool: options.tool,
@@ -35,6 +44,15 @@ function registerSessionCommands(program) {
35
44
  .option('--json', 'Output as JSON')
36
45
  .action(async (sessionRef, options) => {
37
46
  try {
47
+ await captureTimelineCheckpoint({
48
+ trigger: 'key-event',
49
+ event: 'session.resume',
50
+ summary: `session resume | ref=${sessionRef || 'latest'} | status=${options.status || 'active'}`,
51
+ command: 'sce session resume',
52
+ sessionId: sessionRef || 'latest'
53
+ }, {
54
+ projectPath: process.cwd()
55
+ });
38
56
  const store = new SessionStore(process.cwd());
39
57
  const resumed = await store.resumeSession(sessionRef || 'latest', {
40
58
  status: options.status,
@@ -54,6 +72,15 @@ function registerSessionCommands(program) {
54
72
  .option('--json', 'Output as JSON')
55
73
  .action(async (sessionRef, options) => {
56
74
  try {
75
+ await captureTimelineCheckpoint({
76
+ trigger: 'key-event',
77
+ event: 'session.snapshot',
78
+ summary: `session snapshot | ref=${sessionRef || 'latest'} | status=${options.status || 'inherit'}`,
79
+ command: 'sce session snapshot',
80
+ sessionId: sessionRef || 'latest'
81
+ }, {
82
+ projectPath: process.cwd()
83
+ });
57
84
  const store = new SessionStore(process.cwd());
58
85
  const payload = _parsePayload(options.payload);
59
86
  const snapshotted = await store.snapshotSession(sessionRef || 'latest', {
@@ -0,0 +1,70 @@
1
+ const chalk = require('chalk');
2
+ const { findRelatedSpecs } = require('../spec/related-specs');
3
+
4
+ function normalizeText(value) {
5
+ if (typeof value !== 'string') {
6
+ return '';
7
+ }
8
+ return value.trim();
9
+ }
10
+
11
+ async function runSpecRelatedCommand(options = {}, dependencies = {}) {
12
+ const query = normalizeText(options.query);
13
+ const sceneId = normalizeText(options.scene);
14
+ const specId = normalizeText(options.spec);
15
+
16
+ if (!query && !sceneId && !specId) {
17
+ throw new Error('At least one selector is required: --query or --scene or --spec');
18
+ }
19
+
20
+ const payload = await findRelatedSpecs({
21
+ query,
22
+ sceneId,
23
+ sourceSpecId: specId,
24
+ limit: options.limit
25
+ }, dependencies);
26
+
27
+ if (options.json) {
28
+ console.log(JSON.stringify(payload, null, 2));
29
+ } else {
30
+ console.log(chalk.blue('Related Specs'));
31
+ console.log(` Query: ${payload.query || '(none)'}`);
32
+ console.log(` Scene: ${payload.scene_id || '(none)'}`);
33
+ console.log(` Source Spec: ${payload.source_spec_id || '(none)'}`);
34
+ console.log(` Candidates: ${payload.total_candidates}`);
35
+ for (const item of payload.related_specs) {
36
+ console.log(` - ${item.spec_id} | score=${item.score} | scene=${item.scene_id || 'n/a'}`);
37
+ }
38
+ }
39
+
40
+ return payload;
41
+ }
42
+
43
+ function registerSpecRelatedCommand(program) {
44
+ program
45
+ .command('spec-related')
46
+ .description('Find previously related Specs by query/scene context')
47
+ .option('--query <text>', 'Problem statement or search query')
48
+ .option('--scene <scene-id>', 'Scene id for scene-aligned lookup')
49
+ .option('--spec <spec-id>', 'Use existing spec as query seed')
50
+ .option('--limit <n>', 'Maximum related specs to return', '5')
51
+ .option('--json', 'Output machine-readable JSON')
52
+ .action(async (options) => {
53
+ try {
54
+ await runSpecRelatedCommand(options);
55
+ } catch (error) {
56
+ if (options.json) {
57
+ console.log(JSON.stringify({ success: false, error: error.message }, null, 2));
58
+ } else {
59
+ console.error(chalk.red('❌ spec-related failed:'), error.message);
60
+ }
61
+ process.exit(1);
62
+ }
63
+ });
64
+ }
65
+
66
+ module.exports = {
67
+ runSpecRelatedCommand,
68
+ registerSpecRelatedCommand
69
+ };
70
+
@@ -8,6 +8,8 @@ const {
8
8
  DOMAIN_CHAIN_RELATIVE_PATH,
9
9
  ensureSpecDomainArtifacts
10
10
  } = require('../spec/domain-modeling');
11
+ const { findRelatedSpecs } = require('../spec/related-specs');
12
+ const { captureTimelineCheckpoint } = require('../runtime/project-timeline');
11
13
 
12
14
  const STUDIO_JOB_API_VERSION = 'sce.studio.job/v0.1';
13
15
  const STAGE_ORDER = ['plan', 'generate', 'apply', 'verify', 'release'];
@@ -986,6 +988,25 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
986
988
  projectPath,
987
989
  fileSystem
988
990
  });
991
+ const relatedSpecLookup = await findRelatedSpecs({
992
+ query: normalizeString(options.goal),
993
+ sceneId,
994
+ limit: 8,
995
+ excludeSpecId: domainChainBinding.spec_id || specId || null
996
+ }, {
997
+ projectPath,
998
+ fileSystem
999
+ });
1000
+ const relatedSpecItems = Array.isArray(relatedSpecLookup.related_specs)
1001
+ ? relatedSpecLookup.related_specs.map((item) => ({
1002
+ spec_id: item.spec_id,
1003
+ scene_id: item.scene_id || null,
1004
+ score: Number(item.score || 0),
1005
+ reasons: Array.isArray(item.reasons) ? item.reasons : [],
1006
+ matched_tokens: Array.isArray(item.matched_tokens) ? item.matched_tokens : [],
1007
+ updated_at: item.updated_at || null
1008
+ }))
1009
+ : [];
989
1010
 
990
1011
  const paths = resolveStudioPaths(projectPath);
991
1012
  await ensureStudioDirectories(paths, fileSystem);
@@ -1013,7 +1034,9 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
1013
1034
  domain_chain_spec_id: domainChainBinding.spec_id || null,
1014
1035
  domain_chain_path: domainChainBinding.chain_path || null,
1015
1036
  domain_chain_summary: domainChainBinding.summary || null,
1016
- domain_chain_reason: domainChainBinding.reason || null
1037
+ domain_chain_reason: domainChainBinding.reason || null,
1038
+ related_specs_total: Number(relatedSpecLookup.total_candidates || 0),
1039
+ related_specs_top: relatedSpecItems
1017
1040
  }
1018
1041
  };
1019
1042
 
@@ -1040,11 +1063,18 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
1040
1063
  summary: domainChainBinding.summary || null,
1041
1064
  context: domainChainBinding.context || null,
1042
1065
  updated_at: domainChainBinding.updated_at || null
1066
+ },
1067
+ related_specs: {
1068
+ query: relatedSpecLookup.query || '',
1069
+ scene_id: relatedSpecLookup.scene_id || null,
1070
+ total_candidates: Number(relatedSpecLookup.total_candidates || 0),
1071
+ items: relatedSpecItems
1043
1072
  }
1044
1073
  },
1045
1074
  scene: {
1046
1075
  id: sceneId,
1047
- spec_id: domainChainBinding.spec_id || specId || null
1076
+ spec_id: domainChainBinding.spec_id || specId || null,
1077
+ related_spec_ids: relatedSpecItems.map((item) => item.spec_id)
1048
1078
  },
1049
1079
  session: {
1050
1080
  policy: 'mandatory.scene-primary',
@@ -1073,7 +1103,9 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
1073
1103
  domain_chain_resolved: domainChainBinding.resolved === true,
1074
1104
  domain_chain_source: domainChainBinding.source || 'none',
1075
1105
  domain_chain_spec_id: domainChainBinding.spec_id || null,
1076
- domain_chain_path: domainChainBinding.chain_path || null
1106
+ domain_chain_path: domainChainBinding.chain_path || null,
1107
+ related_specs_total: Number(relatedSpecLookup.total_candidates || 0),
1108
+ related_spec_ids: relatedSpecItems.map((item) => item.spec_id)
1077
1109
  }, fileSystem);
1078
1110
  await writeLatestJob(paths, jobId, fileSystem);
1079
1111
 
@@ -1605,8 +1637,30 @@ async function runStudioEventsCommand(options = {}, dependencies = {}) {
1605
1637
  return payload;
1606
1638
  }
1607
1639
 
1608
- async function runStudioCommand(handler, options) {
1640
+ async function runStudioCommand(handler, options, stageName = '') {
1609
1641
  try {
1642
+ const stage = normalizeString(stageName) || 'unknown';
1643
+ const sceneId = normalizeString(options && options.scene);
1644
+ const summaryGoal = normalizeString(options && options.goal);
1645
+ const fromChat = normalizeString(options && options.fromChat);
1646
+ const summaryParts = [
1647
+ 'studio',
1648
+ stage,
1649
+ sceneId ? `scene=${sceneId}` : '',
1650
+ summaryGoal ? `goal=${summaryGoal}` : '',
1651
+ fromChat ? `chat=${fromChat}` : ''
1652
+ ].filter(Boolean);
1653
+
1654
+ await captureTimelineCheckpoint({
1655
+ trigger: 'key-event',
1656
+ event: `studio.${stage}`,
1657
+ summary: summaryParts.join(' | '),
1658
+ command: `sce studio ${stage}`.trim(),
1659
+ sceneId
1660
+ }, {
1661
+ projectPath: process.cwd()
1662
+ });
1663
+
1610
1664
  await handler(options);
1611
1665
  } catch (error) {
1612
1666
  console.error(chalk.red(`Studio command failed: ${error.message}`));
@@ -1629,7 +1683,7 @@ function registerStudioCommands(program) {
1629
1683
  .option('--target <target>', 'Target integration profile', 'default')
1630
1684
  .option('--job <job-id>', 'Reuse an explicit studio job id')
1631
1685
  .option('--json', 'Print machine-readable JSON output')
1632
- .action(async (options) => runStudioCommand(runStudioPlanCommand, options));
1686
+ .action(async (options) => runStudioCommand(runStudioPlanCommand, options, 'plan'));
1633
1687
 
1634
1688
  studio
1635
1689
  .command('generate')
@@ -1639,7 +1693,7 @@ function registerStudioCommands(program) {
1639
1693
  .option('--patch-bundle <id>', 'Explicit patch bundle id')
1640
1694
  .option('--job <job-id>', 'Studio job id (defaults to latest)')
1641
1695
  .option('--json', 'Print machine-readable JSON output')
1642
- .action(async (options) => runStudioCommand(runStudioGenerateCommand, options));
1696
+ .action(async (options) => runStudioCommand(runStudioGenerateCommand, options, 'generate'));
1643
1697
 
1644
1698
  studio
1645
1699
  .command('apply')
@@ -1649,7 +1703,7 @@ function registerStudioCommands(program) {
1649
1703
  .option('--require-auth', 'Require authorization even when policy is advisory')
1650
1704
  .option('--job <job-id>', 'Studio job id (defaults to latest)')
1651
1705
  .option('--json', 'Print machine-readable JSON output')
1652
- .action(async (options) => runStudioCommand(runStudioApplyCommand, options));
1706
+ .action(async (options) => runStudioCommand(runStudioApplyCommand, options, 'apply'));
1653
1707
 
1654
1708
  studio
1655
1709
  .command('verify')
@@ -1657,7 +1711,7 @@ function registerStudioCommands(program) {
1657
1711
  .option('--profile <profile>', 'Verification profile', 'standard')
1658
1712
  .option('--job <job-id>', 'Studio job id (defaults to latest)')
1659
1713
  .option('--json', 'Print machine-readable JSON output')
1660
- .action(async (options) => runStudioCommand(runStudioVerifyCommand, options));
1714
+ .action(async (options) => runStudioCommand(runStudioVerifyCommand, options, 'verify'));
1661
1715
 
1662
1716
  studio
1663
1717
  .command('release')
@@ -1669,14 +1723,14 @@ function registerStudioCommands(program) {
1669
1723
  .option('--release-ref <ref>', 'Explicit release reference/tag')
1670
1724
  .option('--job <job-id>', 'Studio job id (defaults to latest)')
1671
1725
  .option('--json', 'Print machine-readable JSON output')
1672
- .action(async (options) => runStudioCommand(runStudioReleaseCommand, options));
1726
+ .action(async (options) => runStudioCommand(runStudioReleaseCommand, options, 'release'));
1673
1727
 
1674
1728
  studio
1675
1729
  .command('resume')
1676
1730
  .description('Inspect current studio job and next action')
1677
1731
  .option('--job <job-id>', 'Studio job id (defaults to latest)')
1678
1732
  .option('--json', 'Print machine-readable JSON output')
1679
- .action(async (options) => runStudioCommand(runStudioResumeCommand, options));
1733
+ .action(async (options) => runStudioCommand(runStudioResumeCommand, options, 'resume'));
1680
1734
 
1681
1735
  studio
1682
1736
  .command('events')
@@ -1684,7 +1738,7 @@ function registerStudioCommands(program) {
1684
1738
  .option('--job <job-id>', 'Studio job id (defaults to latest)')
1685
1739
  .option('--limit <number>', 'Maximum number of recent events to return', '50')
1686
1740
  .option('--json', 'Print machine-readable JSON output')
1687
- .action(async (options) => runStudioCommand(runStudioEventsCommand, options));
1741
+ .action(async (options) => runStudioCommand(runStudioEventsCommand, options, 'events'));
1688
1742
 
1689
1743
  studio
1690
1744
  .command('rollback')
@@ -1694,7 +1748,7 @@ function registerStudioCommands(program) {
1694
1748
  .option('--auth-password <password>', 'Authorization password for protected rollback action')
1695
1749
  .option('--require-auth', 'Require authorization even when policy is advisory')
1696
1750
  .option('--json', 'Print machine-readable JSON output')
1697
- .action(async (options) => runStudioCommand(runStudioRollbackCommand, options));
1751
+ .action(async (options) => runStudioCommand(runStudioRollbackCommand, options, 'rollback'));
1698
1752
  }
1699
1753
 
1700
1754
  module.exports = {