nccgs 2.1.0 → 2.1.1
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/.claude/agents/nccgs-adversarial-reviewer.md +1 -1
- package/.claude/agents/nccgs-engine-programmer.md +1 -1
- package/.claude/agents/nccgs-fast-implementer.md +1 -1
- package/.claude/agents/nccgs-game-designer.md +1 -1
- package/.claude/agents/nccgs-gameplay-programmer.md +1 -1
- package/.claude/agents/nccgs-qa-engineer.md +1 -1
- package/.claude/agents/nccgs-task-scout.md +1 -1
- package/.claude/agents/nccgs-ui-programmer.md +1 -1
- package/.claude/agents/nccgs-unity-build-specialist.md +1 -1
- package/.claude/agents/nccgs-unity-implementer.md +1 -1
- package/.claude/agents/nccgs-unity-systems-specialist.md +1 -1
- package/.claude/agents/nccgs-unity-ui-specialist.md +1 -1
- package/.claude/agents/nccgs-verification-engineer.md +1 -1
- package/.claude/nccgs/MEMORY.md +3 -1
- package/.claude/nccgs/VERSION +1 -1
- package/.claude/nccgs/constitution.md +187 -176
- package/.claude/nccgs/hooks/closure-guard.mjs +2 -0
- package/.claude/nccgs/hooks/unity-transport-guard.mjs +11 -0
- package/.claude/nccgs/pipeline.json +260 -260
- package/.claude/nccgs/protocols/context-packets.md +5 -0
- package/.claude/nccgs/protocols/model-routing.md +46 -46
- package/.claude/nccgs/protocols/review-handoff.md +21 -1
- package/.claude/nccgs/protocols/shared-workflow-context.md +33 -0
- package/.claude/nccgs/protocols/unity-boundary.md +20 -0
- package/.claude/nccgs/routing.generated.md +4 -4
- package/.claude/nccgs/settings.fragment.json +84 -24
- package/.claude/nccgs/studio.json +53 -53
- package/.claude/nccgs/tools/autonomy.mjs +13 -0
- package/.claude/nccgs/tools/command-tokens.mjs +22 -0
- package/.claude/nccgs/tools/hook-transport.mjs +34 -0
- package/.claude/nccgs/tools/native-completion.mjs +10 -1
- package/.claude/nccgs/tools/returned-handoff.mjs +11 -2
- package/.claude/nccgs/tools/shared-context-guard.mjs +60 -0
- package/.claude/nccgs/tools/shared-context.mjs +25 -0
- package/.claude/nccgs/tools/tool-guard.mjs +120 -131
- package/.claude/nccgs/tools/unity-transport.mjs +170 -0
- package/.claude/nccgs/workflow-catalog.json +4 -4
- package/CHANGELOG.md +235 -200
- package/CLAUDE.md +10 -8
- package/MEMORY.md +109 -60
- package/README.md +206 -191
- package/UPGRADING.md +130 -118
- package/VERSION +1 -1
- package/docs/ARCHITECTURE.md +131 -131
- package/docs/ASSET-PRODUCTION-GRAPHIFY-PLAN.md +230 -0
- package/docs/HUONG-DAN-MIGRATE-VA-SU-DUNG.md +117 -116
- package/docs/NCCGS-1.5.md +237 -237
- package/docs/PROJECT-POLICY.md +94 -94
- package/docs/RELEASE-STATUS.md +17 -9
- package/docs/UNINSTALL.md +3 -3
- package/docs/WFK17-HOOK-REPAIR.md +71 -0
- package/docs/WORKFLOWS.md +85 -85
- package/package.json +1 -1
- package/scaffold/.nccgs/project.yaml +109 -108
- package/scripts/benchmark-v15.mjs +173 -173
- package/scripts/doctor.mjs +261 -261
- package/scripts/install.mjs +530 -529
- package/scripts/run.mjs +5 -4
- package/scripts/supervise.mjs +4 -0
- package/scripts/uninstall.mjs +292 -292
- package/scripts/validate.mjs +285 -280
- package/tests/autonomy.test.mjs +126 -113
- package/tests/design-readiness.test.mjs +199 -199
- package/tests/fixtures/compiler-shutdown.cs +67 -64
- package/tests/fixtures/native-provider.mjs +61 -0
- package/tests/framework.test.mjs +740 -740
- package/tests/hook-repair.test.mjs +106 -0
- package/tests/lifecycle-v15.test.mjs +346 -300
- package/tests/native-completion-notices.test.mjs +181 -0
- package/tests/phase1.test.mjs +427 -416
- package/tests/phase2.test.mjs +250 -250
- package/tests/policy-v15.test.mjs +234 -234
- package/tests/returned-handoff-notices.test.mjs +170 -0
- package/tests/runtime-v15.test.mjs +1197 -1191
- package/tests/task-slices.test.mjs +285 -285
- package/tests/unity-transport.test.mjs +348 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import crypto from 'node:crypto';
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import test from 'node:test';
|
|
9
|
+
import { managedHookKey, requiredHookPresent } from '../.claude/nccgs/tools/hook-transport.mjs';
|
|
10
|
+
import { safeSharedContextCommand } from '../.claude/nccgs/tools/shared-context-guard.mjs';
|
|
11
|
+
|
|
12
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
|
+
const sha = file => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
14
|
+
const fragment = JSON.parse(fs.readFileSync(path.join(root, '.claude/nccgs/settings.fragment.json')));
|
|
15
|
+
const hooks = Object.values(fragment.hooks).flatMap(groups => groups.flatMap(x => x.hooks));
|
|
16
|
+
|
|
17
|
+
test('hook identity migrates exact legacy commands without claiming unrelated node hooks', () => {
|
|
18
|
+
const keys = hooks.map(managedHookKey);
|
|
19
|
+
// The Unity CLI-only guard already exists in settings.fragment.json; R11 adds no hook.
|
|
20
|
+
const expectedIdentities = [
|
|
21
|
+
['.claude/nccgs/hooks/session-start.mjs'],
|
|
22
|
+
['.claude/nccgs/hooks/unity-transport-guard.mjs'],
|
|
23
|
+
['.claude/nccgs/hooks/protect-git.mjs'],
|
|
24
|
+
['.claude/nccgs/hooks/protect-write.mjs'],
|
|
25
|
+
['.claude/nccgs/hooks/closure-guard.mjs', 'pre'],
|
|
26
|
+
['.claude/nccgs/hooks/pre-compact.mjs'],
|
|
27
|
+
['.claude/nccgs/hooks/post-compact.mjs'],
|
|
28
|
+
['.claude/nccgs/hooks/agent-audit.mjs', 'start'],
|
|
29
|
+
['.claude/nccgs/hooks/agent-audit.mjs', 'stop'],
|
|
30
|
+
['.claude/nccgs/hooks/session-stop.mjs'],
|
|
31
|
+
['.claude/nccgs/hooks/closure-guard.mjs', 'stop'],
|
|
32
|
+
['.claude/nccgs/hooks/runtime-observation.mjs', 'failure'],
|
|
33
|
+
['.claude/nccgs/hooks/runtime-observation.mjs'],
|
|
34
|
+
].map(identity => JSON.stringify(identity));
|
|
35
|
+
assert.equal(keys.length, expectedIdentities.length);
|
|
36
|
+
assert.deepEqual([...new Set(keys)].sort(), expectedIdentities.sort());
|
|
37
|
+
for (const h of hooks) {
|
|
38
|
+
const legacy = { type: 'command', command: `node "${h.args[0]}"${h.args.length > 1 ? ' ' + h.args[1] : ''}` };
|
|
39
|
+
assert.equal(managedHookKey(legacy), managedHookKey(h));
|
|
40
|
+
assert.equal(managedHookKey({ ...legacy, command: legacy.command + ' && echo owner' }), null);
|
|
41
|
+
}
|
|
42
|
+
assert.equal(managedHookKey({ type: 'command', command: 'node', args: ['custom-owner.mjs'] }), null);
|
|
43
|
+
const expected = fragment.hooks.PreToolUse[2].hooks[0];
|
|
44
|
+
for (const change of [{args:[expected.args[0],'stop']},{args:[]},{onFailure:'continue'},{if:'Read(*)'},{once:true},{async:true}]) {
|
|
45
|
+
assert.equal(requiredHookPresent({ ...expected, ...change }, expected), false);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('installed doctor checks argv and failure policy; update/uninstall preserve custom node argv', () => {
|
|
50
|
+
const parent = fs.realpathSync(os.tmpdir()), dir = fs.mkdtempSync(path.join(parent, 'nccgs-hook-repair-'));
|
|
51
|
+
const project = path.join(dir, 'Unity project Việt');
|
|
52
|
+
fs.mkdirSync(project);
|
|
53
|
+
for (const marker of ['Assets','Packages','ProjectSettings']) fs.mkdirSync(path.join(project, marker));
|
|
54
|
+
const env = { ...process.env };
|
|
55
|
+
for (const key of ['CLAUDECODE','CLAUDE_CODE_ENTRYPOINT','NCCGS_PROJECT_ROOT','NCCGS_TASK_AUTONOMY']) delete env[key];
|
|
56
|
+
const run = (...argv) => spawnSync(process.execPath,[path.join(root,'scripts/cli.mjs'),...argv,'--project',project],{cwd:dir,env,encoding:'utf8',windowsHide:true});
|
|
57
|
+
const write = (name,text) => { const f=path.join(project,name);fs.mkdirSync(path.dirname(f),{recursive:true});fs.writeFileSync(f,text);return f; };
|
|
58
|
+
try {
|
|
59
|
+
let r=run('install','--autonomy','guarded');assert.equal(r.status,0,r.stderr+r.stdout);
|
|
60
|
+
const file=path.join(project,'.claude/settings.json'), settings=JSON.parse(fs.readFileSync(file));
|
|
61
|
+
const custom={type:'command',command:'node',args:['owner-check.mjs'],timeout:8};
|
|
62
|
+
settings.hooks.SessionStart[0].hooks.push(custom);
|
|
63
|
+
const native=settings.hooks.SessionStart[0].hooks[0];
|
|
64
|
+
native.command=`node "${native.args[0]}"`;delete native.args;
|
|
65
|
+
fs.writeFileSync(file,JSON.stringify(settings));
|
|
66
|
+
r=run('update');assert.equal(r.status,0,r.stderr+r.stdout);
|
|
67
|
+
const updated=JSON.parse(fs.readFileSync(file));
|
|
68
|
+
assert.ok(updated.hooks.SessionStart.flatMap(x=>x.hooks).some(h=>JSON.stringify(h)===JSON.stringify(custom)));
|
|
69
|
+
assert.equal(run('doctor').status,0);
|
|
70
|
+
const good=JSON.stringify(updated);
|
|
71
|
+
for (const delta of [{args:[]},{onFailure:'continue'},{if:'Read(*)'},{once:true}]) {
|
|
72
|
+
const changed=JSON.parse(good);Object.assign(changed.hooks.PreToolUse[2].hooks[0],delta);
|
|
73
|
+
fs.writeFileSync(file,JSON.stringify(changed));assert.notEqual(run('doctor').status,0);
|
|
74
|
+
}
|
|
75
|
+
fs.writeFileSync(file,good);
|
|
76
|
+
// Trusted external binding is process-local and admits only exact read commands.
|
|
77
|
+
const wf=path.join(dir,'pinned workflow'), add=(rel,text)=>{const f=path.join(wf,rel);fs.mkdirSync(path.dirname(f),{recursive:true});fs.writeFileSync(f,text);return f;};
|
|
78
|
+
const pkg=add('package.json',JSON.stringify({name:'agent-project-workflow-candidate',version:'1.7.0'}));
|
|
79
|
+
const cli=add('tools/workflow.mjs','throw new Error("guard tests never execute the external reader");\n');
|
|
80
|
+
const manifest=add('implementation/ARTIFACT_MANIFEST.json',JSON.stringify({files:[pkg,cli].map(f=>({path:path.relative(wf,f).split(path.sep).join('/'),size:fs.statSync(f).size,sha256:sha(f)}))}));
|
|
81
|
+
const binding={NCCGS_SHARED_WORKFLOW_ROOT:wf,NCCGS_SHARED_WORKFLOW_MANIFEST_SHA256:sha(manifest)};
|
|
82
|
+
const config=write('docs/workflow/workflow.config.json','{}\n');
|
|
83
|
+
const base=`node .claude/nccgs/tools/shared-context.mjs --project "${project}" --workflow-root "${wf}"`;
|
|
84
|
+
const status=base+' --action status', query=base+' --action query --text Restore';
|
|
85
|
+
const common=`node "${cli}" status --config "${config}"`;
|
|
86
|
+
for(const command of [status,query,common])assert.equal(safeSharedContextCommand(command,project,binding),true,command);
|
|
87
|
+
assert.equal(safeSharedContextCommand(status,project,{}),false);
|
|
88
|
+
const rejected=[base+' --action refresh',base+' --action setup',query+' --hold x',query+' --text Other',status+'; node evil',status+' && node evil',status+' > out',query.replace('Restore','"$(evil)"'),query.replace('Restore',"'a; evil'"),common.replace(' status ',' graph '),common+' --project other',status.replace(wf,dir),status.replace(project,dir),status.replace('node ','node --eval '),'node --version'];
|
|
89
|
+
for(const command of rejected)assert.equal(safeSharedContextCommand(command,project,binding),false,command);
|
|
90
|
+
const saved=fs.readFileSync(cli);fs.appendFileSync(cli,'// changed reader\n');assert.equal(safeSharedContextCommand(common,project,binding),false);fs.writeFileSync(cli,saved);
|
|
91
|
+
assert.equal(safeSharedContextCommand(status,project,{...binding,NCCGS_SHARED_WORKFLOW_MANIFEST_SHA256:'0'.repeat(64)}),false);
|
|
92
|
+
const bridge=path.join(project,'.claude/nccgs/tools/shared-context.mjs'),original=fs.readFileSync(bridge);
|
|
93
|
+
fs.appendFileSync(bridge,'// changed bridge\n');assert.equal(safeSharedContextCommand(status,project,binding),false);fs.writeFileSync(bridge,original);
|
|
94
|
+
// Direct guard invocation exercises the same code the exec-form hook runs.
|
|
95
|
+
const hook=path.join(project,'.claude/nccgs/hooks/closure-guard.mjs');
|
|
96
|
+
const invoke=command=>spawnSync(process.execPath,[hook,'pre'],{cwd:project,env:{...env,...binding,NCCGS_PROJECT_ROOT:project,CLAUDE_PROJECT_DIR:project},input:JSON.stringify({tool_name:'PowerShell',tool_input:{command},session_id:'guard-test'}),encoding:'utf8'});
|
|
97
|
+
assert.equal(invoke(status).status,0);
|
|
98
|
+
assert.equal(invoke('node --version').status,2);
|
|
99
|
+
const removed=run('uninstall');assert.equal(removed.status,0,removed.stderr+removed.stdout);
|
|
100
|
+
const retained=JSON.parse(fs.readFileSync(file));
|
|
101
|
+
assert.ok(retained.hooks.SessionStart.flatMap(x=>x.hooks).some(h=>JSON.stringify(h)===JSON.stringify(custom)));
|
|
102
|
+
} finally {
|
|
103
|
+
assert.equal(path.dirname(fs.realpathSync(dir)),parent);assert.ok(path.basename(dir).startsWith('nccgs-hook-repair-'));
|
|
104
|
+
assert.equal(fs.lstatSync(dir).isSymbolicLink(),false);fs.rmSync(dir,{recursive:true,force:true});
|
|
105
|
+
}
|
|
106
|
+
});
|