@sanqianx/project-knowledge 4.7.0 → 5.0.0
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/.agents/plugins/marketplace.json +17 -17
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +836 -799
- package/README.md +58 -41
- package/_modules/knowledge-engine/README.md +91 -0
- package/_modules/knowledge-engine/package.json +38 -0
- package/_modules/knowledge-engine/src/analysis-contract.js +25 -0
- package/_modules/knowledge-engine/src/analysis-worker.js +54 -0
- package/_modules/knowledge-engine/src/bin.js +52 -0
- package/_modules/knowledge-engine/src/bridge-client.js +24 -0
- package/_modules/knowledge-engine/src/coordinator.js +263 -0
- package/_modules/knowledge-engine/src/headless-analyzer.js +75 -0
- package/_modules/knowledge-engine/src/memories.js +85 -0
- package/_modules/knowledge-engine/src/retrieval.js +116 -0
- package/_modules/knowledge-engine/src/runtime/README.md +7 -0
- package/_modules/knowledge-engine/src/runtime/atomic-file.js +204 -0
- package/_modules/knowledge-engine/src/runtime/automation-config.js +140 -0
- package/_modules/knowledge-engine/src/runtime/commit-conversation-binder.js +249 -0
- package/_modules/knowledge-engine/src/runtime/commit-processing-ledger.js +26 -0
- package/_modules/knowledge-engine/src/runtime/commit-prompt.js +202 -0
- package/_modules/knowledge-engine/src/runtime/commit-reconciler.js +432 -0
- package/_modules/knowledge-engine/src/runtime/content-hash.js +4 -0
- package/_modules/knowledge-engine/src/runtime/contracts.js +279 -0
- package/_modules/knowledge-engine/src/runtime/conversation-exclusions.js +204 -0
- package/_modules/knowledge-engine/src/runtime/conversation-store.js +387 -0
- package/_modules/knowledge-engine/src/runtime/evidence-bundle.js +228 -0
- package/_modules/knowledge-engine/src/runtime/git-runner.js +62 -0
- package/_modules/knowledge-engine/src/runtime/knowledge-promotion.js +444 -0
- package/_modules/knowledge-engine/src/runtime/knowledge-retrieval-service.js +402 -0
- package/_modules/knowledge-engine/src/runtime/layout.js +44 -0
- package/_modules/knowledge-engine/src/runtime/markdown-knowledge-indexer.js +230 -0
- package/_modules/knowledge-engine/src/runtime/scanner.js +298 -0
- package/_modules/knowledge-engine/src/server.js +154 -0
- package/_modules/knowledge-engine/src/store.js +236 -0
- package/_modules/knowledge-engine/ui/app.css +172 -0
- package/_modules/knowledge-engine/ui/app.js +297 -0
- package/_modules/knowledge-engine/ui/index.html +95 -0
- package/_site/README.md +30 -30
- package/_site/_test/ai-profile-resolver-test.js +137 -137
- package/_site/_test/automation-queue-test.js +14 -14
- package/_site/_test/automation-ui-test.js +40 -40
- package/_site/_test/background-task-registry-test.js +43 -43
- package/_site/_test/baseline-schema-test.js +91 -91
- package/_site/_test/chat-claudecodeui-match-test.js +92 -92
- package/_site/_test/claude-executable-discovery-test.js +62 -62
- package/_site/_test/claude-workbench-test.js +170 -170
- package/_site/_test/codex-conversation-projection-test.js +2 -1
- package/_site/_test/commit-conversation-binding-test.js +95 -95
- package/_site/_test/commit-evidence-test.js +110 -110
- package/_site/_test/conversation-api-test.js +109 -109
- package/_site/_test/conversation-store-test.js +136 -136
- package/_site/_test/desktop-browser-compat-test.js +28 -28
- package/_site/_test/desktop-hook-runtime-regression-test.js +204 -204
- package/_site/_test/explicit-commit-processor-test.js +37 -37
- package/_site/_test/fixtures/make-git-repos.js +12 -1
- package/_site/_test/folder-picker-output-test.js +19 -19
- package/_site/_test/fresh-hook-reimport-test.js +53 -0
- package/_site/_test/fresh-product-data-test.js +42 -0
- package/_site/_test/full-integration-e2e-test.js +165 -165
- package/_site/_test/git-validation-test.js +2 -2
- package/_site/_test/hook-runtime-endpoint-test.js +88 -88
- package/_site/_test/hook-status-repair-api-test.js +152 -152
- package/_site/_test/hook-trigger-test.js +85 -85
- package/_site/_test/import-preflight-api-test.js +234 -233
- package/_site/_test/index-writer-concurrency-test.js +135 -135
- package/_site/_test/integration-adapters-test.js +171 -171
- package/_site/_test/integration-surface-coverage-test.js +164 -164
- package/_site/_test/knowledge-language-control-test.js +170 -170
- package/_site/_test/knowledge-promotion-recovery-test.js +260 -260
- package/_site/_test/knowledge-query-test.js +54 -54
- package/_site/_test/knowledge-retrieval-service-test.js +77 -77
- package/_site/_test/knowledge-storage-startup-test.js +52 -52
- package/_site/_test/knowledge-store-logs-supervision-test.js +89 -89
- package/_site/_test/logging-api-test.js +89 -89
- package/_site/_test/logging-sse-no-gap-test.js +88 -88
- package/_site/_test/logging-ui-test.js +106 -104
- package/_site/_test/markdown-delta-overlay-test.js +85 -85
- package/_site/_test/markdown-maintenance-api-test.js +75 -75
- package/_site/_test/mcp-server-test.js +149 -149
- package/_site/_test/module-artifact-integrity-test.js +27 -0
- package/_site/_test/module-boundary-test.js +30 -0
- package/_site/_test/module-bridge-eventbridge-test.js +7 -0
- package/_site/_test/module-model-configuration-test.js +44 -0
- package/_site/_test/module-process-lifecycle-test.js +24 -0
- package/_site/_test/module-stream-proxy-test.js +34 -0
- package/_site/_test/non-release-ci-test.js +34 -34
- package/_site/_test/offline-boundary-isolation-test.js +32 -32
- package/_site/_test/packaged-ui-smoke-test.js +11 -9
- package/_site/_test/path-consistency-test.js +151 -151
- package/_site/_test/pending-sweep-test.js +9 -9
- package/_site/_test/post-commit-automation-test.js +87 -87
- package/_site/_test/product-diagnostics-ui-test.js +75 -0
- package/_site/_test/product-import-ui-test.js +92 -0
- package/_site/_test/project-delete-recovery-test.js +64 -64
- package/_site/_test/project-goal-editor-test.js +144 -144
- package/_site/_test/project-lifecycle-transaction-test.js +106 -106
- package/_site/_test/prompt-settings-test.js +115 -115
- package/_site/_test/protected-architecture-gate-test.js +122 -122
- package/_site/_test/refactor-characterization-test.js +36 -36
- package/_site/_test/release-version-sync-test.js +1 -1
- package/_site/_test/requirement-recorder-test.js +173 -173
- package/_site/_test/run-all-tests.js +159 -156
- package/_site/_test/shared-contracts-test.js +58 -58
- package/_site/_test/startup-analysis-disabled-test.js +10 -10
- package/_site/_test/storage-foundation-test.js +78 -78
- package/_site/_test/structured-logger-test.js +101 -101
- package/_site/_test/tracking-start-test.js +109 -108
- package/_site/_test/workbench-permission-test.js +117 -117
- package/_site/_test/workspace-ui-contract-test.js +28 -43
- package/_site/lib/ai-profile-resolver.js +78 -78
- package/_site/lib/ai-workspace.js +101 -101
- package/_site/lib/automation-config.js +140 -140
- package/_site/lib/bridge-adapter.js +1 -0
- package/_site/lib/bridge-consumer-service.js +402 -402
- package/_site/lib/claude-cli-runner.js +1510 -1510
- package/_site/lib/commit-conversation-binder.js +234 -234
- package/_site/lib/commit-processing-ledger.js +27 -27
- package/_site/lib/commit-prompt.js +202 -202
- package/_site/lib/commit-reconciler.js +406 -406
- package/_site/lib/contracts.js +284 -279
- package/_site/lib/conversation-query-service.js +144 -144
- package/_site/lib/conversation-store.js +385 -385
- package/_site/lib/data-dir.js +6 -74
- package/_site/lib/engine-connection.js +67 -0
- package/_site/lib/evidence-bundle.js +228 -228
- package/_site/lib/folder-picker-output.js +21 -21
- package/_site/lib/git-runner.js +62 -62
- package/_site/lib/github-team-store.js +1172 -1172
- package/_site/lib/hook-manager.js +203 -202
- package/_site/lib/index-service.js +173 -173
- package/_site/lib/integration-installer.js +908 -907
- package/_site/lib/kb-framework.js +176 -176
- package/_site/lib/kb-validator.js +124 -124
- package/_site/lib/knowledge-promotion.js +435 -435
- package/_site/lib/knowledge-retrieval-service.js +401 -401
- package/_site/lib/knowledge-tool-runtime.js +359 -359
- package/_site/lib/llm-client.js +161 -161
- package/_site/lib/markdown-knowledge-indexer.js +230 -230
- package/_site/lib/module-bridge.js +191 -36
- package/_site/lib/post-commit-automation.js +87 -87
- package/_site/lib/product-data.js +24 -0
- package/_site/lib/project-lifecycle-service.js +535 -497
- package/_site/lib/project-store.js +268 -262
- package/_site/lib/requirement-recorder.js +278 -278
- package/_site/lib/runtime-endpoint.js +155 -155
- package/_site/lib/scanner.js +298 -298
- package/_site/lib/server-app.js +1579 -1520
- package/_site/lib/settings-store.js +133 -113
- package/_site/lib/storage-layout.js +162 -162
- package/_site/lib/structured-logger.js +574 -574
- package/_site/scripts/folder-picker.ps1 +156 -156
- package/_site/scripts/hook-trigger.js +157 -156
- package/_site/scripts/install-module-candidates.js +69 -0
- package/_site/scripts/pack-module-candidates.js +72 -0
- package/_site/scripts/sync-release-version.js +2 -2
- package/_site/scripts/vendor-modules.js +47 -0
- package/_site/scripts/verify-module-runtime.js +45 -0
- package/_site/scripts/verify-product-runtime.js +34 -0
- package/bin/project-knowledge-mcp.js +194 -194
- package/docs/README.zh-CN.md +42 -29
- package/docs/fresh-v5-start.md +28 -0
- package/docs/project-registry-schema.md +22 -22
- package/docs/testing-strategy.md +33 -33
- package/module-runtime-manifest.json +220 -0
- package/package.json +14 -9
- package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
- package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
- package/plugins/project-knowledge/.mcp.json +1 -1
- package/plugins/project-knowledge/opencode/project-knowledge.md +3 -3
- package/plugins/project-knowledge/skills/project-knowledge/SKILL.md +28 -28
- package/templates/project-readme.md +34 -34
- package/ui/favicon.svg +38 -38
- package/ui/index.html +52 -149
- package/ui/product.css +9 -0
- package/ui/product.js +412 -0
- package/vendor-manifest.json +45 -0
- package/_site/_test/data-dir-migration-test.js +0 -77
- package/_site/_test/import-ui-flow-test.js +0 -171
- package/_site/_test/knowledge-migration-test.js +0 -75
- package/_site/_test/legacy-forward-compat-test.js +0 -257
- package/_site/_test/legacy-project-upgrade-e2e-test.js +0 -363
- package/_site/_test/p0-data-migration-characterization-test.js +0 -37
- package/_site/_test/p0-e2e-gate-test.js +0 -347
- package/_site/_test/project-control-panel-task14-test.js +0 -63
- package/_site/_test/project-layout-v2-migration-test.js +0 -127
- package/_site/_test/server-runtime-migration-safety-test.js +0 -27
- package/_site/_test/task15-20-ui-flow-test.js +0 -148
- package/_site/_test/ui-i18n-toggle-test.js +0 -114
- package/_site/_test/ui-smoke-test.js +0 -73
- package/_site/_test/v4122-upgrade-data-contract-test.js +0 -56
- package/_site/lib/data-state-classifier.js +0 -40
- package/_site/lib/legacy-data-manifest.js +0 -26
- package/_site/lib/migration-service.js +0 -442
- package/ui/app.css +0 -58
- package/ui/app.js +0 -607
- package/ui/i18n.js +0 -146
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
const assert = require('assert');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { spawnSync } = require('child_process');
|
|
6
|
-
const { StorageLayout } = require('../lib/storage-layout');
|
|
7
|
-
const { SettingsStore } = require('../lib/settings-store');
|
|
8
|
-
const { ProjectRegistryStore } = require('../lib/project-registry-store');
|
|
9
|
-
const { ProjectStore } = require('../lib/project-store');
|
|
10
|
-
const { ProjectLifecycleService } = require('../lib/project-lifecycle-service');
|
|
11
|
-
const realHookManager = require('../lib/hook-manager');
|
|
12
|
-
|
|
13
|
-
const ROOT = path.resolve(__dirname, '..', '..');
|
|
14
|
-
const TRIGGER = path.join(ROOT, '_site', 'scripts', 'hook-trigger.js');
|
|
15
|
-
|
|
16
|
-
function git(repo, args) {
|
|
17
|
-
const result = spawnSync('git', ['-C', repo, ...args], { encoding: 'utf8', windowsHide: true, env: { ...process.env, GIT_TERMINAL_PROMPT: '0' } });
|
|
18
|
-
if (result.status !== 0) throw new Error(result.stderr || `git ${args.join(' ')} failed`);
|
|
19
|
-
return String(result.stdout || '').trim();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function createRepo(root, name, withCommit = true) {
|
|
23
|
-
const repo = path.join(root, name);
|
|
24
|
-
fs.mkdirSync(repo, { recursive: true });
|
|
25
|
-
git(repo, ['init', '--initial-branch=main']);
|
|
26
|
-
git(repo, ['config', 'user.email', 'life@example.invalid']);
|
|
27
|
-
git(repo, ['config', 'user.name', 'Lifecycle Test']);
|
|
28
|
-
if (withCommit) {
|
|
29
|
-
fs.writeFileSync(path.join(repo, 'README.md'), `# ${name}\n`);
|
|
30
|
-
git(repo, ['add', 'README.md']);
|
|
31
|
-
git(repo, ['commit', '-m', 'initial']);
|
|
32
|
-
}
|
|
33
|
-
return repo;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
(async () => {
|
|
37
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-lifecycle-'));
|
|
38
|
-
const dataDir = path.join(root, 'data');
|
|
39
|
-
const knowledgeRoot = path.join(root, 'knowledge');
|
|
40
|
-
const layout = new StorageLayout({ dataDir });
|
|
41
|
-
const settings = new SettingsStore({ layout });
|
|
42
|
-
const registry = new ProjectRegistryStore({ layout });
|
|
43
|
-
const projects = new ProjectStore({ layout });
|
|
44
|
-
await settings.initialize({ knowledge: { rootPath: knowledgeRoot } });
|
|
45
|
-
await registry.initialize();
|
|
46
|
-
const lifecycle = new ProjectLifecycleService({
|
|
47
|
-
layout,
|
|
48
|
-
settingsStore: settings,
|
|
49
|
-
registryStore: registry,
|
|
50
|
-
projectStore: projects,
|
|
51
|
-
triggerScriptPath: TRIGGER,
|
|
52
|
-
bridgeAdapter: { async getHighWatermark() { return { status: 'captured', cursor: 73 }; } },
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const repo = createRepo(root, 'repo-a');
|
|
56
|
-
const claude = path.join(repo, 'CLAUDE.md');
|
|
57
|
-
fs.writeFileSync(claude, '# user only\n');
|
|
58
|
-
const imported = await lifecycle.importProject({ localPath: repo, projectId: 'project-life-a', storageName: 'repo-a-stable' });
|
|
59
|
-
assert.strictEqual(imported.ok, true);
|
|
60
|
-
assert.strictEqual(imported.config.aiProfileId, null, 'import must not require an AI profile');
|
|
61
|
-
assert.strictEqual(imported.state.trackingStartCommit, git(repo, ['rev-parse', 'HEAD']));
|
|
62
|
-
assert.strictEqual(imported.state.conversationBaselineCursor, 73, 'import must freeze the current Bridge cursor as its conversation baseline');
|
|
63
|
-
assert.deepStrictEqual(fs.readdirSync(imported.config.knowledgePath), [], 'import must not create TODO knowledge files');
|
|
64
|
-
assert.strictEqual(fs.readFileSync(claude, 'utf8'), '# user only\n');
|
|
65
|
-
assert.strictEqual(realHookManager.readHookStatus({ repoPath: repo, projectId: imported.projectId }).installed, true);
|
|
66
|
-
await assert.rejects(lifecycle.importProject({ localPath: repo }), /already imported/);
|
|
67
|
-
|
|
68
|
-
const conflictRepo = createRepo(root, 'repo-conflict');
|
|
69
|
-
const conflictPath = path.join(knowledgeRoot, 'conflict-stable');
|
|
70
|
-
fs.mkdirSync(conflictPath, { recursive: true });
|
|
71
|
-
fs.writeFileSync(path.join(conflictPath, 'user.md'), 'do not overwrite');
|
|
72
|
-
await assert.rejects(lifecycle.importProject({ localPath: conflictRepo, projectId: 'project-conflict', storageName: 'conflict-stable' }), error => error.code === 'MIGRATION_TARGET_CONFLICT');
|
|
73
|
-
assert(!registry.listIds().includes('project-conflict'));
|
|
74
|
-
assert.strictEqual(fs.readFileSync(path.join(conflictPath, 'user.md'), 'utf8'), 'do not overwrite');
|
|
75
|
-
|
|
76
|
-
const failingRepo = createRepo(root, 'repo-failing');
|
|
77
|
-
const failingHooks = { ...realHookManager, installHook() { throw new Error('injected Hook failure'); } };
|
|
78
|
-
const failingLifecycle = new ProjectLifecycleService({ layout, settingsStore: settings, registryStore: registry, projectStore: projects, triggerScriptPath: TRIGGER, hookManager: failingHooks });
|
|
79
|
-
await assert.rejects(failingLifecycle.importProject({ localPath: failingRepo, projectId: 'project-failing', storageName: 'failing-stable' }));
|
|
80
|
-
assert(!registry.listIds().includes('project-failing'));
|
|
81
|
-
assert(!fs.existsSync(layout.getProjectMetadataDir('project-failing')));
|
|
82
|
-
assert(!fs.existsSync(path.join(knowledgeRoot, 'failing-stable')));
|
|
83
|
-
|
|
84
|
-
const emptyRepo = createRepo(root, 'repo-empty', false);
|
|
85
|
-
const empty = await lifecycle.importProject({ localPath: emptyRepo, projectId: 'project-empty', storageName: 'empty-stable' });
|
|
86
|
-
assert.strictEqual(empty.state.trackingMode, 'empty-repo');
|
|
87
|
-
assert.strictEqual(empty.state.trackingStartCommit, null);
|
|
88
|
-
|
|
89
|
-
const failingDelete = new ProjectLifecycleService({
|
|
90
|
-
layout, settingsStore: settings, registryStore: registry, projectStore: projects, triggerScriptPath: TRIGGER,
|
|
91
|
-
hookManager: { ...realHookManager, uninstallHook() { throw new Error('injected uninstall failure'); } },
|
|
92
|
-
});
|
|
93
|
-
await assert.rejects(failingDelete.deleteProject(imported.projectId));
|
|
94
|
-
assert(registry.listIds().includes(imported.projectId), 'Hook uninstall failure must preserve registration');
|
|
95
|
-
|
|
96
|
-
const knowledgePath = imported.config.knowledgePath;
|
|
97
|
-
const deleted = await lifecycle.deleteProject(imported.projectId);
|
|
98
|
-
assert.strictEqual(deleted.removedKnowledge, false);
|
|
99
|
-
assert(fs.existsSync(knowledgePath), 'default delete must preserve external knowledge directory');
|
|
100
|
-
assert(!registry.listIds().includes(imported.projectId));
|
|
101
|
-
|
|
102
|
-
console.log('project-lifecycle-transaction-test PASS');
|
|
103
|
-
})().catch(error => {
|
|
104
|
-
console.error(error.stack || error.message);
|
|
105
|
-
process.exitCode = 1;
|
|
106
|
-
});
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawnSync } = require('child_process');
|
|
6
|
+
const { StorageLayout } = require('../lib/storage-layout');
|
|
7
|
+
const { SettingsStore } = require('../lib/settings-store');
|
|
8
|
+
const { ProjectRegistryStore } = require('../lib/project-registry-store');
|
|
9
|
+
const { ProjectStore } = require('../lib/project-store');
|
|
10
|
+
const { ProjectLifecycleService } = require('../lib/project-lifecycle-service');
|
|
11
|
+
const realHookManager = require('../lib/hook-manager');
|
|
12
|
+
|
|
13
|
+
const ROOT = path.resolve(__dirname, '..', '..');
|
|
14
|
+
const TRIGGER = path.join(ROOT, '_site', 'scripts', 'hook-trigger.js');
|
|
15
|
+
|
|
16
|
+
function git(repo, args) {
|
|
17
|
+
const result = spawnSync('git', ['-C', repo, ...args], { encoding: 'utf8', windowsHide: true, env: { ...process.env, GIT_TERMINAL_PROMPT: '0' } });
|
|
18
|
+
if (result.status !== 0) throw new Error(result.stderr || `git ${args.join(' ')} failed`);
|
|
19
|
+
return String(result.stdout || '').trim();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function createRepo(root, name, withCommit = true) {
|
|
23
|
+
const repo = path.join(root, name);
|
|
24
|
+
fs.mkdirSync(repo, { recursive: true });
|
|
25
|
+
git(repo, ['init', '--initial-branch=main']);
|
|
26
|
+
git(repo, ['config', 'user.email', 'life@example.invalid']);
|
|
27
|
+
git(repo, ['config', 'user.name', 'Lifecycle Test']);
|
|
28
|
+
if (withCommit) {
|
|
29
|
+
fs.writeFileSync(path.join(repo, 'README.md'), `# ${name}\n`);
|
|
30
|
+
git(repo, ['add', 'README.md']);
|
|
31
|
+
git(repo, ['commit', '-m', 'initial']);
|
|
32
|
+
}
|
|
33
|
+
return repo;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
(async () => {
|
|
37
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-lifecycle-'));
|
|
38
|
+
const dataDir = path.join(root, 'data');
|
|
39
|
+
const knowledgeRoot = path.join(root, 'knowledge');
|
|
40
|
+
const layout = new StorageLayout({ dataDir });
|
|
41
|
+
const settings = new SettingsStore({ layout });
|
|
42
|
+
const registry = new ProjectRegistryStore({ layout });
|
|
43
|
+
const projects = new ProjectStore({ layout });
|
|
44
|
+
await settings.initialize({ knowledge: { rootPath: knowledgeRoot } });
|
|
45
|
+
await registry.initialize();
|
|
46
|
+
const lifecycle = new ProjectLifecycleService({
|
|
47
|
+
layout,
|
|
48
|
+
settingsStore: settings,
|
|
49
|
+
registryStore: registry,
|
|
50
|
+
projectStore: projects,
|
|
51
|
+
triggerScriptPath: TRIGGER,
|
|
52
|
+
bridgeAdapter: { async getHighWatermark() { return { status: 'captured', cursor: 73 }; } },
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const repo = createRepo(root, 'repo-a');
|
|
56
|
+
const claude = path.join(repo, 'CLAUDE.md');
|
|
57
|
+
fs.writeFileSync(claude, '# user only\n');
|
|
58
|
+
const imported = await lifecycle.importProject({ localPath: repo, projectId: 'project-life-a', storageName: 'repo-a-stable' });
|
|
59
|
+
assert.strictEqual(imported.ok, true);
|
|
60
|
+
assert.strictEqual(imported.config.aiProfileId, null, 'import must not require an AI profile');
|
|
61
|
+
assert.strictEqual(imported.state.trackingStartCommit, git(repo, ['rev-parse', 'HEAD']));
|
|
62
|
+
assert.strictEqual(imported.state.conversationBaselineCursor, 73, 'import must freeze the current Bridge cursor as its conversation baseline');
|
|
63
|
+
assert.deepStrictEqual(fs.readdirSync(imported.config.knowledgePath), [], 'import must not create TODO knowledge files');
|
|
64
|
+
assert.strictEqual(fs.readFileSync(claude, 'utf8'), '# user only\n');
|
|
65
|
+
assert.strictEqual(realHookManager.readHookStatus({ repoPath: repo, projectId: imported.projectId }).installed, true);
|
|
66
|
+
await assert.rejects(lifecycle.importProject({ localPath: repo }), /already imported/);
|
|
67
|
+
|
|
68
|
+
const conflictRepo = createRepo(root, 'repo-conflict');
|
|
69
|
+
const conflictPath = path.join(knowledgeRoot, 'conflict-stable');
|
|
70
|
+
fs.mkdirSync(conflictPath, { recursive: true });
|
|
71
|
+
fs.writeFileSync(path.join(conflictPath, 'user.md'), 'do not overwrite');
|
|
72
|
+
await assert.rejects(lifecycle.importProject({ localPath: conflictRepo, projectId: 'project-conflict', storageName: 'conflict-stable' }), error => error.code === 'MIGRATION_TARGET_CONFLICT');
|
|
73
|
+
assert(!registry.listIds().includes('project-conflict'));
|
|
74
|
+
assert.strictEqual(fs.readFileSync(path.join(conflictPath, 'user.md'), 'utf8'), 'do not overwrite');
|
|
75
|
+
|
|
76
|
+
const failingRepo = createRepo(root, 'repo-failing');
|
|
77
|
+
const failingHooks = { ...realHookManager, installHook() { throw new Error('injected Hook failure'); } };
|
|
78
|
+
const failingLifecycle = new ProjectLifecycleService({ layout, settingsStore: settings, registryStore: registry, projectStore: projects, triggerScriptPath: TRIGGER, hookManager: failingHooks });
|
|
79
|
+
await assert.rejects(failingLifecycle.importProject({ localPath: failingRepo, projectId: 'project-failing', storageName: 'failing-stable' }));
|
|
80
|
+
assert(!registry.listIds().includes('project-failing'));
|
|
81
|
+
assert(!fs.existsSync(layout.getProjectMetadataDir('project-failing')));
|
|
82
|
+
assert(!fs.existsSync(path.join(knowledgeRoot, 'failing-stable')));
|
|
83
|
+
|
|
84
|
+
const emptyRepo = createRepo(root, 'repo-empty', false);
|
|
85
|
+
const empty = await lifecycle.importProject({ localPath: emptyRepo, projectId: 'project-empty', storageName: 'empty-stable' });
|
|
86
|
+
assert.strictEqual(empty.state.trackingMode, 'empty-repo');
|
|
87
|
+
assert.strictEqual(empty.state.trackingStartCommit, null);
|
|
88
|
+
|
|
89
|
+
const failingDelete = new ProjectLifecycleService({
|
|
90
|
+
layout, settingsStore: settings, registryStore: registry, projectStore: projects, triggerScriptPath: TRIGGER,
|
|
91
|
+
hookManager: { ...realHookManager, uninstallHook() { throw new Error('injected uninstall failure'); } },
|
|
92
|
+
});
|
|
93
|
+
await assert.rejects(failingDelete.deleteProject(imported.projectId));
|
|
94
|
+
assert(registry.listIds().includes(imported.projectId), 'Hook uninstall failure must preserve registration');
|
|
95
|
+
|
|
96
|
+
const knowledgePath = imported.config.knowledgePath;
|
|
97
|
+
const deleted = await lifecycle.deleteProject(imported.projectId);
|
|
98
|
+
assert.strictEqual(deleted.removedKnowledge, false);
|
|
99
|
+
assert(fs.existsSync(knowledgePath), 'default delete must preserve external knowledge directory');
|
|
100
|
+
assert(!registry.listIds().includes(imported.projectId));
|
|
101
|
+
|
|
102
|
+
console.log('project-lifecycle-transaction-test PASS');
|
|
103
|
+
})().catch(error => {
|
|
104
|
+
console.error(error.stack || error.message);
|
|
105
|
+
process.exitCode = 1;
|
|
106
|
+
});
|
|
@@ -1,115 +1,115 @@
|
|
|
1
|
-
// _site/_test/prompt-settings-test.js
|
|
2
|
-
//
|
|
3
|
-
// T12: Prompt Settings on the canonical analysis path. Verifies:
|
|
4
|
-
// - promptOverrides from settings flow into the canonical commit
|
|
5
|
-
// prompt rendering (via commit-prompt.js / automation-config.js);
|
|
6
|
-
// - reset to default restores the original prompt hash;
|
|
7
|
-
// - malformed templates are rejected;
|
|
8
|
-
// - the canonical CommitReconciler.prepareClaim path picks up the
|
|
9
|
-
// configured override without bypassing evidence manifests or
|
|
10
|
-
// prompt hashes.
|
|
11
|
-
|
|
12
|
-
const assert = require('assert');
|
|
13
|
-
const fs = require('fs');
|
|
14
|
-
const os = require('os');
|
|
15
|
-
const path = require('path');
|
|
16
|
-
const { StorageLayout } = require('../lib/storage-layout');
|
|
17
|
-
const { SettingsStore, defaultSettings } = require('../lib/settings-store');
|
|
18
|
-
const { renderCommitPrompt } = require('../lib/commit-prompt');
|
|
19
|
-
const { normalizeAutomationConfig, DEFAULT_COMMIT_PROMPT_TEMPLATE } = require('../lib/automation-config');
|
|
20
|
-
|
|
21
|
-
(async () => {
|
|
22
|
-
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-prompt-settings-'));
|
|
23
|
-
const layout = new StorageLayout({ dataDir });
|
|
24
|
-
const settings = new SettingsStore({ layout });
|
|
25
|
-
await settings.initialize({ knowledge: { rootPath: '' } });
|
|
26
|
-
|
|
27
|
-
const fakeEvidence = {
|
|
28
|
-
schema: 'commit-evidence/v1',
|
|
29
|
-
commitSha: 'abc1234567890abcdef1234567890abcdef123456',
|
|
30
|
-
parents: [],
|
|
31
|
-
author: 'tester',
|
|
32
|
-
date: '2026-08-20T00:00:00Z',
|
|
33
|
-
subject: 'test commit',
|
|
34
|
-
branch: 'main',
|
|
35
|
-
patchBase: 'parent',
|
|
36
|
-
patchMode: 'parent',
|
|
37
|
-
files: [],
|
|
38
|
-
patchHash: 'sha256:' + '0'.repeat(64),
|
|
39
|
-
patchBytes: 0,
|
|
40
|
-
patchInline: true,
|
|
41
|
-
patchChunked: false,
|
|
42
|
-
patchOmitted: false,
|
|
43
|
-
patchLimitBytes: 0,
|
|
44
|
-
omittedReason: null,
|
|
45
|
-
evidenceBundle: null,
|
|
46
|
-
evidenceHash: 'sha256:' + '0'.repeat(64),
|
|
47
|
-
patch: '',
|
|
48
|
-
};
|
|
49
|
-
const baseInput = {
|
|
50
|
-
config: { projectId: 'p', displayName: 'P', knowledgePath: '', repoPath: '', automation: {} },
|
|
51
|
-
evidence: fakeEvidence,
|
|
52
|
-
requirements: [],
|
|
53
|
-
conversationSnapshot: null,
|
|
54
|
-
existingKnowledge: { entries: [], totalBytes: 0 },
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// Case 1: default prompt renders the canonical template.
|
|
58
|
-
{
|
|
59
|
-
const config = normalizeAutomationConfig({});
|
|
60
|
-
assert.strictEqual(config.commitPromptTemplate, DEFAULT_COMMIT_PROMPT_TEMPLATE);
|
|
61
|
-
const rendered = renderCommitPrompt(baseInput);
|
|
62
|
-
assert(rendered.prompt.length > 0);
|
|
63
|
-
assert(rendered.promptHash && rendered.promptHash.startsWith('sha256:'), 'promptHash must be a sha256 digest');
|
|
64
|
-
assert(rendered.prompt.includes('你正在根据一个 Git Commit'), 'default template must render the canonical Chinese prompt');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Case 2: promptOverrides.commitPromptTemplate changes the rendered
|
|
68
|
-
// prompt deterministically.
|
|
69
|
-
{
|
|
70
|
-
const override = 'CUSTOM-PROMPT-TEMPLATE {{commitHash}} {{commitSubject}} {{commitAuthor}}';
|
|
71
|
-
const renderedDefault = renderCommitPrompt(baseInput);
|
|
72
|
-
const renderedCustom = renderCommitPrompt({
|
|
73
|
-
...baseInput,
|
|
74
|
-
config: { ...baseInput.config, automation: { commitPromptTemplate: override } },
|
|
75
|
-
});
|
|
76
|
-
assert.notStrictEqual(renderedCustom.promptHash, renderedDefault.promptHash, 'override must change prompt hash');
|
|
77
|
-
assert(renderedCustom.prompt.includes('CUSTOM-PROMPT-TEMPLATE'), 'override template must appear in rendered prompt');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Case 3: reset to default restores the original template + hash.
|
|
81
|
-
{
|
|
82
|
-
const originalConfig = normalizeAutomationConfig({});
|
|
83
|
-
const overrideConfig = normalizeAutomationConfig({ commitPromptTemplate: 'OVERRIDE {{commitHash}}' });
|
|
84
|
-
const resetConfig = normalizeAutomationConfig({ ...overrideConfig, commitPromptTemplate: '' });
|
|
85
|
-
assert.strictEqual(resetConfig.commitPromptTemplate, originalConfig.commitPromptTemplate, 'empty override restores default');
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Case 4: malformed template (no required tokens) — the renderer must
|
|
89
|
-
// still produce a deterministic hash; downstream code (prepareClaim)
|
|
90
|
-
// is what enforces minimum content via ensureRequiredSections.
|
|
91
|
-
{
|
|
92
|
-
const rendered = renderCommitPrompt({
|
|
93
|
-
...baseInput,
|
|
94
|
-
config: { ...baseInput.config, automation: { commitPromptTemplate: 'NO-TOKENS-HERE' } },
|
|
95
|
-
});
|
|
96
|
-
assert(rendered.prompt.includes('NO-TOKENS-HERE'));
|
|
97
|
-
assert(rendered.promptHash.startsWith('sha256:'));
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Case 5: settings.promptOverrides is wired through settings updatePatch.
|
|
101
|
-
// Persisting a prompt override via the canonical settings API must
|
|
102
|
-
// appear in subsequent reads (the next analyzer invocation will pick
|
|
103
|
-
// up the new template via this path).
|
|
104
|
-
{
|
|
105
|
-
await settings.updatePatch({ promptOverrides: { commitPromptTemplate: 'NEW {{commitHash}} {{commitSubject}}' } });
|
|
106
|
-
const current = settings.read();
|
|
107
|
-
assert.strictEqual(current.promptOverrides.commitPromptTemplate, 'NEW {{commitHash}} {{commitSubject}}');
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
111
|
-
console.log('prompt-settings-test PASS');
|
|
112
|
-
})().catch(error => {
|
|
113
|
-
console.error(error.stack || error.message);
|
|
114
|
-
process.exit(1);
|
|
115
|
-
});
|
|
1
|
+
// _site/_test/prompt-settings-test.js
|
|
2
|
+
//
|
|
3
|
+
// T12: Prompt Settings on the canonical analysis path. Verifies:
|
|
4
|
+
// - promptOverrides from settings flow into the canonical commit
|
|
5
|
+
// prompt rendering (via commit-prompt.js / automation-config.js);
|
|
6
|
+
// - reset to default restores the original prompt hash;
|
|
7
|
+
// - malformed templates are rejected;
|
|
8
|
+
// - the canonical CommitReconciler.prepareClaim path picks up the
|
|
9
|
+
// configured override without bypassing evidence manifests or
|
|
10
|
+
// prompt hashes.
|
|
11
|
+
|
|
12
|
+
const assert = require('assert');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const os = require('os');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { StorageLayout } = require('../lib/storage-layout');
|
|
17
|
+
const { SettingsStore, defaultSettings } = require('../lib/settings-store');
|
|
18
|
+
const { renderCommitPrompt } = require('../lib/commit-prompt');
|
|
19
|
+
const { normalizeAutomationConfig, DEFAULT_COMMIT_PROMPT_TEMPLATE } = require('../lib/automation-config');
|
|
20
|
+
|
|
21
|
+
(async () => {
|
|
22
|
+
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-prompt-settings-'));
|
|
23
|
+
const layout = new StorageLayout({ dataDir });
|
|
24
|
+
const settings = new SettingsStore({ layout });
|
|
25
|
+
await settings.initialize({ knowledge: { rootPath: '' } });
|
|
26
|
+
|
|
27
|
+
const fakeEvidence = {
|
|
28
|
+
schema: 'commit-evidence/v1',
|
|
29
|
+
commitSha: 'abc1234567890abcdef1234567890abcdef123456',
|
|
30
|
+
parents: [],
|
|
31
|
+
author: 'tester',
|
|
32
|
+
date: '2026-08-20T00:00:00Z',
|
|
33
|
+
subject: 'test commit',
|
|
34
|
+
branch: 'main',
|
|
35
|
+
patchBase: 'parent',
|
|
36
|
+
patchMode: 'parent',
|
|
37
|
+
files: [],
|
|
38
|
+
patchHash: 'sha256:' + '0'.repeat(64),
|
|
39
|
+
patchBytes: 0,
|
|
40
|
+
patchInline: true,
|
|
41
|
+
patchChunked: false,
|
|
42
|
+
patchOmitted: false,
|
|
43
|
+
patchLimitBytes: 0,
|
|
44
|
+
omittedReason: null,
|
|
45
|
+
evidenceBundle: null,
|
|
46
|
+
evidenceHash: 'sha256:' + '0'.repeat(64),
|
|
47
|
+
patch: '',
|
|
48
|
+
};
|
|
49
|
+
const baseInput = {
|
|
50
|
+
config: { projectId: 'p', displayName: 'P', knowledgePath: '', repoPath: '', automation: {} },
|
|
51
|
+
evidence: fakeEvidence,
|
|
52
|
+
requirements: [],
|
|
53
|
+
conversationSnapshot: null,
|
|
54
|
+
existingKnowledge: { entries: [], totalBytes: 0 },
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Case 1: default prompt renders the canonical template.
|
|
58
|
+
{
|
|
59
|
+
const config = normalizeAutomationConfig({});
|
|
60
|
+
assert.strictEqual(config.commitPromptTemplate, DEFAULT_COMMIT_PROMPT_TEMPLATE);
|
|
61
|
+
const rendered = renderCommitPrompt(baseInput);
|
|
62
|
+
assert(rendered.prompt.length > 0);
|
|
63
|
+
assert(rendered.promptHash && rendered.promptHash.startsWith('sha256:'), 'promptHash must be a sha256 digest');
|
|
64
|
+
assert(rendered.prompt.includes('你正在根据一个 Git Commit'), 'default template must render the canonical Chinese prompt');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Case 2: promptOverrides.commitPromptTemplate changes the rendered
|
|
68
|
+
// prompt deterministically.
|
|
69
|
+
{
|
|
70
|
+
const override = 'CUSTOM-PROMPT-TEMPLATE {{commitHash}} {{commitSubject}} {{commitAuthor}}';
|
|
71
|
+
const renderedDefault = renderCommitPrompt(baseInput);
|
|
72
|
+
const renderedCustom = renderCommitPrompt({
|
|
73
|
+
...baseInput,
|
|
74
|
+
config: { ...baseInput.config, automation: { commitPromptTemplate: override } },
|
|
75
|
+
});
|
|
76
|
+
assert.notStrictEqual(renderedCustom.promptHash, renderedDefault.promptHash, 'override must change prompt hash');
|
|
77
|
+
assert(renderedCustom.prompt.includes('CUSTOM-PROMPT-TEMPLATE'), 'override template must appear in rendered prompt');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Case 3: reset to default restores the original template + hash.
|
|
81
|
+
{
|
|
82
|
+
const originalConfig = normalizeAutomationConfig({});
|
|
83
|
+
const overrideConfig = normalizeAutomationConfig({ commitPromptTemplate: 'OVERRIDE {{commitHash}}' });
|
|
84
|
+
const resetConfig = normalizeAutomationConfig({ ...overrideConfig, commitPromptTemplate: '' });
|
|
85
|
+
assert.strictEqual(resetConfig.commitPromptTemplate, originalConfig.commitPromptTemplate, 'empty override restores default');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Case 4: malformed template (no required tokens) — the renderer must
|
|
89
|
+
// still produce a deterministic hash; downstream code (prepareClaim)
|
|
90
|
+
// is what enforces minimum content via ensureRequiredSections.
|
|
91
|
+
{
|
|
92
|
+
const rendered = renderCommitPrompt({
|
|
93
|
+
...baseInput,
|
|
94
|
+
config: { ...baseInput.config, automation: { commitPromptTemplate: 'NO-TOKENS-HERE' } },
|
|
95
|
+
});
|
|
96
|
+
assert(rendered.prompt.includes('NO-TOKENS-HERE'));
|
|
97
|
+
assert(rendered.promptHash.startsWith('sha256:'));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Case 5: settings.promptOverrides is wired through settings updatePatch.
|
|
101
|
+
// Persisting a prompt override via the canonical settings API must
|
|
102
|
+
// appear in subsequent reads (the next analyzer invocation will pick
|
|
103
|
+
// up the new template via this path).
|
|
104
|
+
{
|
|
105
|
+
await settings.updatePatch({ promptOverrides: { commitPromptTemplate: 'NEW {{commitHash}} {{commitSubject}}' } });
|
|
106
|
+
const current = settings.read();
|
|
107
|
+
assert.strictEqual(current.promptOverrides.commitPromptTemplate, 'NEW {{commitHash}} {{commitSubject}}');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
111
|
+
console.log('prompt-settings-test PASS');
|
|
112
|
+
})().catch(error => {
|
|
113
|
+
console.error(error.stack || error.message);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
});
|