Closes #3347 Config: - Add graphify.auto_update (default false) to manifests: sdk/shared/config-defaults.manifest.json, config-schema.manifest.json Hook: - hooks/gsd-graphify-update.sh — PostToolUse Bash matcher - Gates: tool_name=Bash, HEAD-advancing git op, CI=unset, in git repo, current branch == default branch (git.base_branch override or main/ master/trunk fallback), graphify.enabled && graphify.auto_update both true, graphify on PATH, no live PID lock - Writes .planning/graphs/.last-build-status.json with status=running synchronously, then detaches hooks/lib/gsd-graphify-rebuild.sh - hooks/lib/gsd-graphify-rebuild.sh — detached rebuild runner - PID-lock acquire + trap-on-exit cleanup - graphify update . then cp graphify-out/* → .planning/graphs/ - Status file rewritten to status=ok|failed with exit_code, duration_ms, head_at_build - Portable detach (subshell + disown, no setsid dependency) Installer: - bin/install.js: register hook as PostToolUse Bash matcher (5s timeout) - Add to gsdHooks uninstall list and expectedShHooks warning list Planner / researcher status surface (issue #3347 reviewer must-have AC): - agents/gsd-planner.md and agents/gsd-phase-researcher.md load_graph_context steps now read .last-build-status.json and surface: running → "rebuild in flight"; failed → "auto-rebuild FAILED at {ts}, context is from prior build"; ok with stale head_at_build → "HEAD has advanced since last build" Settings: - get-shit-done/workflows/settings.md adds "Graph auto-update" question with No-Recommended default; bullets and update_config block updated Inventory: - docs/INVENTORY.md hook count 12 → 13 with new row - docs/INVENTORY-MANIFEST.json regenerated Tests: - tests/feat-3347-graphify-auto-update-config.test.cjs (8 tests): isValidConfigKey accepts graphify.auto_update, CANONICAL_CONFIG_DEFAULTS default false, config-set round-trip, sibling key preservation - tests/feat-3347-graphify-auto-update-hook.test.cjs (18 tests): all bail paths (non-Bash, non-HEAD-advancing, enabled=false, auto_update=false, CI=true, non-default-branch, missing graphify bin, live-PID lock), dispatch path with mock graphify bin (sync running status + detached transition to ok/failed), stale-PID lock, all five HEAD-advancing command matchers, git.base_branch override Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
150 lines
4.9 KiB
JavaScript
150 lines
4.9 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Regression tests for #3347 — opt-in auto-update of the knowledge graph
|
|
* after main HEAD advances.
|
|
*
|
|
* This file covers the config-key surface: the new `graphify.auto_update`
|
|
* key must be a valid config key, default to false, persist via config-set,
|
|
* and round-trip via config-get. The runtime hook behavior is covered in
|
|
* tests/feat-3347-graphify-auto-update-hook.test.cjs.
|
|
*
|
|
* Default-off discipline (issue #3347 acceptance criteria):
|
|
* - `graphify.auto_update` defaults to `false` so existing users see no
|
|
* behavior change after upgrade.
|
|
* - Opt-in via /gsd:settings or `gsd-tools config-set graphify.auto_update true`.
|
|
*/
|
|
|
|
const { describe, test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const { createTempProject, cleanup, runGsdTools } = require('./helpers.cjs');
|
|
|
|
const {
|
|
VALID_CONFIG_KEYS,
|
|
isValidConfigKey,
|
|
} = require('../get-shit-done/bin/lib/config-schema.cjs');
|
|
|
|
const {
|
|
CONFIG_DEFAULTS: CANONICAL_CONFIG_DEFAULTS,
|
|
} = require('../get-shit-done/bin/lib/configuration.generated.cjs');
|
|
|
|
describe('#3347 — graphify.auto_update is a registered config key', () => {
|
|
test('VALID_CONFIG_KEYS contains graphify.auto_update', () => {
|
|
assert.ok(
|
|
VALID_CONFIG_KEYS.has('graphify.auto_update'),
|
|
'graphify.auto_update must be in VALID_CONFIG_KEYS so config-set accepts it',
|
|
);
|
|
});
|
|
|
|
test('isValidConfigKey accepts graphify.auto_update', () => {
|
|
assert.ok(
|
|
isValidConfigKey('graphify.auto_update'),
|
|
'isValidConfigKey must return true for graphify.auto_update',
|
|
);
|
|
});
|
|
|
|
test('isValidConfigKey still accepts the pre-existing graphify.enabled key', () => {
|
|
assert.ok(
|
|
isValidConfigKey('graphify.enabled'),
|
|
'regression guard: graphify.enabled must remain a valid key',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('#3347 — graphify.auto_update defaults to false', () => {
|
|
test('CANONICAL_CONFIG_DEFAULTS.graphify.auto_update is false', () => {
|
|
assert.ok(
|
|
CANONICAL_CONFIG_DEFAULTS.graphify !== undefined,
|
|
'CANONICAL_CONFIG_DEFAULTS must expose a graphify section',
|
|
);
|
|
assert.strictEqual(
|
|
CANONICAL_CONFIG_DEFAULTS.graphify.auto_update,
|
|
false,
|
|
'graphify.auto_update default must be false (opt-in per issue #3347 AC)',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('#3347 — config-set graphify.auto_update round-trips', () => {
|
|
test('config-set graphify.auto_update true succeeds', (t) => {
|
|
const tmpDir = createTempProject();
|
|
t.after(() => cleanup(tmpDir));
|
|
|
|
const result = runGsdTools(
|
|
['config-set', 'graphify.auto_update', 'true'],
|
|
tmpDir,
|
|
);
|
|
assert.ok(
|
|
result.success,
|
|
[
|
|
'config-set graphify.auto_update true should succeed,',
|
|
'got:',
|
|
'stdout: ' + result.output,
|
|
'stderr: ' + result.error,
|
|
].join('\n'),
|
|
);
|
|
});
|
|
|
|
test('config-set graphify.auto_update true writes to config.json', (t) => {
|
|
const tmpDir = createTempProject();
|
|
t.after(() => cleanup(tmpDir));
|
|
|
|
runGsdTools(['config-set', 'graphify.auto_update', 'true'], tmpDir);
|
|
|
|
const configPath = path.join(tmpDir, '.planning', 'config.json');
|
|
assert.ok(
|
|
fs.existsSync(configPath),
|
|
'.planning/config.json must exist after config-set',
|
|
);
|
|
|
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
assert.strictEqual(
|
|
config.graphify?.auto_update,
|
|
true,
|
|
[
|
|
'Expected graphify.auto_update: true in config.json,',
|
|
'got: ' + JSON.stringify(config.graphify),
|
|
].join('\n'),
|
|
);
|
|
});
|
|
|
|
test('config-set graphify.auto_update false persists too', (t) => {
|
|
const tmpDir = createTempProject();
|
|
t.after(() => cleanup(tmpDir));
|
|
|
|
runGsdTools(['config-set', 'graphify.auto_update', 'true'], tmpDir);
|
|
runGsdTools(['config-set', 'graphify.auto_update', 'false'], tmpDir);
|
|
|
|
const configPath = path.join(tmpDir, '.planning', 'config.json');
|
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
assert.strictEqual(
|
|
config.graphify?.auto_update,
|
|
false,
|
|
'config-set must round-trip true → false',
|
|
);
|
|
});
|
|
|
|
test('config-set graphify.auto_update does not perturb sibling graphify.enabled', (t) => {
|
|
const tmpDir = createTempProject();
|
|
t.after(() => cleanup(tmpDir));
|
|
|
|
runGsdTools(['config-set', 'graphify.enabled', 'true'], tmpDir);
|
|
runGsdTools(['config-set', 'graphify.auto_update', 'true'], tmpDir);
|
|
|
|
const configPath = path.join(tmpDir, '.planning', 'config.json');
|
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
assert.strictEqual(
|
|
config.graphify?.enabled,
|
|
true,
|
|
'graphify.enabled must be preserved when setting graphify.auto_update',
|
|
);
|
|
assert.strictEqual(
|
|
config.graphify?.auto_update,
|
|
true,
|
|
'graphify.auto_update must coexist with graphify.enabled',
|
|
);
|
|
});
|
|
});
|