* feat(#956): add MemPalace memory capability (ADR-857 feature plug-in) Adds an opt-in, default-resilient ADR-857 feature capability that wires MemPalace (local-first memory: MCP server + CLI) into the GSD loop: deliberate recall before discuss/plan and verbatim + temporal-KG capture at phase boundaries. Three memory modes (augment default; kg_backend and replace forward-declared). Master gate mempalace.enabled (default off); every hook onError:skip, zero gates; absent/disabled MemPalace => loop unchanged. Transport is rendered-markdown only — MemPalace runs out-of-process, no third-party code in gsd-core (ADR-857 §7). Capability: capabilities/mempalace/ (manifest + 2 fragments), skills commands/gsd/mempalace-{recall,capture}.md, agent agents/gsd-mempalace-curator.md. Registration: ns-context router, utility cluster, KNOWN_SKILLS, help full.md, model-catalog, copilot install list, size baselines; regenerated capability-registry + inventory manifest. ship:post wired into ship.md (wire-on-demand). HELD on #1196: this capability also declares hooks at discuss:pre and discuss:post, which are structurally un-wireable until the host-loop conformance model covers the discuss phase (discuss-phase.md is not in HOST_LOOP_FILES). The phase6-capstone-conformance gate therefore fails on exactly those two orphaned points by design — see #1196. Once #1196 lands, rebase onto next and the gate goes green with no further change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(#956): backfill changeset PR number (#1201) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
"gsd-framework-selector.md": 6778,
|
||||
"gsd-integration-checker.md": 15141,
|
||||
"gsd-intel-updater.md": 18122,
|
||||
"gsd-mempalace-curator.md": 4160,
|
||||
"gsd-nyquist-auditor.md": 7245,
|
||||
"gsd-pattern-mapper.md": 12487,
|
||||
"gsd-phase-researcher.md": 40611,
|
||||
|
||||
@@ -493,13 +493,14 @@ describe('resolveLoopHooks plan:post — pure function against real registry', (
|
||||
assert.strictEqual(result.activeHooks[0].capId, 'gap-analysis');
|
||||
});
|
||||
|
||||
test('[happy] real registry byLoopPoint plan:post has exactly one gate and no steps or contributions', () => {
|
||||
test('[happy] real registry byLoopPoint plan:post has 1 step (mempalace), 0 contributions, and 1 gate (gap-analysis)', () => {
|
||||
const entry = realRegistry.byLoopPoint['plan:post'];
|
||||
assert.ok(entry, 'plan:post must exist in byLoopPoint');
|
||||
assert.ok(Array.isArray(entry.steps), 'steps must be an array');
|
||||
assert.ok(Array.isArray(entry.contributions), 'contributions must be an array');
|
||||
assert.ok(Array.isArray(entry.gates), 'gates must be an array');
|
||||
assert.strictEqual(entry.steps.length, 0, 'plan:post must have zero steps');
|
||||
assert.strictEqual(entry.steps.length, 1, 'plan:post must have 1 step (mempalace capture)');
|
||||
assert.strictEqual(entry.steps[0].capId, 'mempalace', 'plan:post step must be from mempalace');
|
||||
assert.strictEqual(entry.contributions.length, 0, 'plan:post must have zero contributions');
|
||||
assert.strictEqual(entry.gates.length, 1, 'plan:post must have exactly one gate');
|
||||
assert.strictEqual(entry.gates[0].capId, 'gap-analysis');
|
||||
|
||||
@@ -1438,6 +1438,7 @@ describe('E2E: Copilot full install verification', () => {
|
||||
'gsd-framework-selector.agent.md',
|
||||
'gsd-integration-checker.agent.md',
|
||||
'gsd-intel-updater.agent.md',
|
||||
'gsd-mempalace-curator.agent.md',
|
||||
'gsd-nyquist-auditor.agent.md',
|
||||
'gsd-pattern-mapper.agent.md',
|
||||
'gsd-phase-researcher.agent.md',
|
||||
|
||||
@@ -45,6 +45,8 @@ const KNOWN_SKILLS = new Set([
|
||||
'ingest-docs.md',
|
||||
'manager.md',
|
||||
'map-codebase.md',
|
||||
'mempalace-capture.md',
|
||||
'mempalace-recall.md',
|
||||
'milestone-summary.md',
|
||||
'mvp-phase.md',
|
||||
'new-milestone.md',
|
||||
|
||||
@@ -629,12 +629,14 @@ describe('F. Real registry execute:wave:post shape — guard against accidental
|
||||
`ui.safety-gate onError must be 'halt'; got ${uiGate.onError}`);
|
||||
});
|
||||
|
||||
test('[happy] real registry: execute:wave:post has no steps and no contributions — pure gate point', () => {
|
||||
test('[happy] real registry: execute:wave:post has no steps and 1 contribution (mempalace capture-problems) — gate point with mempalace contribution', () => {
|
||||
const point = realRegistry.byLoopPoint['execute:wave:post'];
|
||||
assert.strictEqual(point.steps.length, 0,
|
||||
`execute:wave:post steps must be empty; got ${point.steps.length}`);
|
||||
assert.strictEqual(point.contributions.length, 0,
|
||||
`execute:wave:post contributions must be empty; got ${point.contributions.length}`);
|
||||
assert.strictEqual(point.contributions.length, 1,
|
||||
`execute:wave:post must have 1 contribution (mempalace); got ${point.contributions.length}`);
|
||||
assert.strictEqual(point.contributions[0].capId, 'mempalace',
|
||||
`execute:wave:post contribution must be from mempalace; got ${point.contributions[0].capId}`);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -585,7 +585,8 @@ describe('ship:post — real registry empty-resolution + resilience to registry
|
||||
}
|
||||
});
|
||||
|
||||
it('[happy] resolveLoopHooks with real capability-registry at ship:post returns empty activeHooks and well-formed byLoopPoint entry', () => {
|
||||
it('[happy] resolveLoopHooks with real capability-registry at ship:post returns empty activeHooks (mempalace step inactive by default) and well-formed byLoopPoint entry with 1 step', () => {
|
||||
// mempalace.enabled defaults to false → step not activated with empty config
|
||||
const resolved = resolveLoopHooks({ point: 'ship:post', registry: realRegistry, config: {} });
|
||||
assert.strictEqual(resolved.point, 'ship:post');
|
||||
assert.ok(Array.isArray(resolved.activeHooks));
|
||||
@@ -593,7 +594,8 @@ describe('ship:post — real registry empty-resolution + resilience to registry
|
||||
|
||||
const entry = realRegistry.byLoopPoint['ship:post'];
|
||||
assert.ok(entry, 'ship:post must be present in real registry byLoopPoint');
|
||||
assert.strictEqual(entry.steps.length, 0, 'ship:post must have 0 steps');
|
||||
assert.strictEqual(entry.steps.length, 1, 'ship:post must have 1 step (mempalace curator)');
|
||||
assert.strictEqual(entry.steps[0].capId, 'mempalace', 'ship:post step must be from mempalace');
|
||||
assert.strictEqual(entry.contributions.length, 0, 'ship:post must have 0 contributions');
|
||||
assert.strictEqual(entry.gates.length, 0, 'ship:post must have 0 gates');
|
||||
});
|
||||
@@ -665,12 +667,13 @@ describe('ship:post — real registry empty-resolution + resilience to registry
|
||||
}
|
||||
});
|
||||
|
||||
it('[empty-resolution] ship:post byLoopPoint entry exists in the real registry with all three arrays empty — no capability has silently self-registered here', () => {
|
||||
it('[happy] ship:post byLoopPoint entry exists in the real registry with 1 step (mempalace), 0 contributions, 0 gates', () => {
|
||||
const entry = realRegistry.byLoopPoint['ship:post'];
|
||||
assert.ok(entry, 'ship:post must be present in real registry byLoopPoint');
|
||||
assert.strictEqual(entry.steps.length, 0, 'ship:post must have 0 steps — accidental registration guard');
|
||||
assert.strictEqual(entry.contributions.length, 0, 'ship:post must have 0 contributions — accidental registration guard');
|
||||
assert.strictEqual(entry.gates.length, 0, 'ship:post must have 0 gates — accidental registration guard');
|
||||
assert.strictEqual(entry.steps.length, 1, 'ship:post must have 1 step (mempalace curator step)');
|
||||
assert.strictEqual(entry.steps[0].capId, 'mempalace', 'ship:post step must be from mempalace capability');
|
||||
assert.strictEqual(entry.contributions.length, 0, 'ship:post must have 0 contributions');
|
||||
assert.strictEqual(entry.gates.length, 0, 'ship:post must have 0 gates');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -715,14 +718,13 @@ describe('CLI contract — missing/invalid point argument (shared across all 6 e
|
||||
// SECTION 8: Parametric empty-point sweep across all 6 points (E2E regression guard)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('Parametric E2E sweep — all 6 empty points return correct envelope shape via real registry', () => {
|
||||
describe('Parametric E2E sweep — 5 empty points return correct envelope shape via real registry (ship:post excluded — mempalace registers 1 step there)', () => {
|
||||
const EMPTY_POINTS = [
|
||||
'discuss:pre',
|
||||
'discuss:post',
|
||||
'execute:pre',
|
||||
'execute:wave:pre',
|
||||
'verify:pre',
|
||||
'ship:post',
|
||||
];
|
||||
|
||||
for (const point of EMPTY_POINTS) {
|
||||
|
||||
@@ -522,22 +522,22 @@ describe('verify:post — envelope shape pins Hyrum\'s Law contract', () => {
|
||||
|
||||
// ─── 10. Real registry byLoopPoint shape check (no drift guard) ───────────────
|
||||
|
||||
describe('verify:post — real registry has exactly 3 steps and 0 contributions+gates', () => {
|
||||
test('[happy] realRegistry.byLoopPoint[verify:post] has 3 steps, 0 contributions, 0 gates', () => {
|
||||
describe('verify:post — real registry has exactly 4 steps and 0 contributions+gates', () => {
|
||||
test('[happy] realRegistry.byLoopPoint[verify:post] has 4 steps, 0 contributions, 0 gates', () => {
|
||||
const entry = realRegistry.byLoopPoint['verify:post'];
|
||||
assert.ok(entry, 'verify:post must exist in registry');
|
||||
assert.strictEqual(entry.steps.length, 3,
|
||||
`Expected 3 steps at verify:post, got ${entry.steps.length}`);
|
||||
assert.strictEqual(entry.steps.length, 4,
|
||||
`Expected 4 steps at verify:post, got ${entry.steps.length}`);
|
||||
assert.strictEqual(entry.contributions.length, 0,
|
||||
`Expected 0 contributions at verify:post, got ${entry.contributions.length}`);
|
||||
assert.strictEqual(entry.gates.length, 0,
|
||||
`Expected 0 gates at verify:post, got ${entry.gates.length}`);
|
||||
});
|
||||
|
||||
test('[happy] registry steps at verify:post have correct capIds in order', () => {
|
||||
test('[happy] registry steps at verify:post have correct capIds in order (mempalace→nyquist→security→ui)', () => {
|
||||
const entry = realRegistry.byLoopPoint['verify:post'];
|
||||
const capIds = entry.steps.map(s => s.capId);
|
||||
assert.deepEqual(capIds, ['nyquist', 'security', 'ui'],
|
||||
`Registry must have steps in nyquist→security→ui order, got ${JSON.stringify(capIds)}`);
|
||||
assert.deepEqual(capIds, ['mempalace', 'nyquist', 'security', 'ui'],
|
||||
`Registry must have steps in mempalace→nyquist→security→ui order, got ${JSON.stringify(capIds)}`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
"settings-advanced.md": 39621,
|
||||
"settings-integrations.md": 15801,
|
||||
"settings.md": 32133,
|
||||
"ship.md": 22534,
|
||||
"ship.md": 24629,
|
||||
"sketch-wrap-up.md": 14223,
|
||||
"sketch.md": 19960,
|
||||
"spec-phase.md": 23094,
|
||||
|
||||
Reference in New Issue
Block a user