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.
Files changed (76) hide show
  1. package/.claude/agents/nccgs-adversarial-reviewer.md +1 -1
  2. package/.claude/agents/nccgs-engine-programmer.md +1 -1
  3. package/.claude/agents/nccgs-fast-implementer.md +1 -1
  4. package/.claude/agents/nccgs-game-designer.md +1 -1
  5. package/.claude/agents/nccgs-gameplay-programmer.md +1 -1
  6. package/.claude/agents/nccgs-qa-engineer.md +1 -1
  7. package/.claude/agents/nccgs-task-scout.md +1 -1
  8. package/.claude/agents/nccgs-ui-programmer.md +1 -1
  9. package/.claude/agents/nccgs-unity-build-specialist.md +1 -1
  10. package/.claude/agents/nccgs-unity-implementer.md +1 -1
  11. package/.claude/agents/nccgs-unity-systems-specialist.md +1 -1
  12. package/.claude/agents/nccgs-unity-ui-specialist.md +1 -1
  13. package/.claude/agents/nccgs-verification-engineer.md +1 -1
  14. package/.claude/nccgs/MEMORY.md +3 -1
  15. package/.claude/nccgs/VERSION +1 -1
  16. package/.claude/nccgs/constitution.md +187 -176
  17. package/.claude/nccgs/hooks/closure-guard.mjs +2 -0
  18. package/.claude/nccgs/hooks/unity-transport-guard.mjs +11 -0
  19. package/.claude/nccgs/pipeline.json +260 -260
  20. package/.claude/nccgs/protocols/context-packets.md +5 -0
  21. package/.claude/nccgs/protocols/model-routing.md +46 -46
  22. package/.claude/nccgs/protocols/review-handoff.md +21 -1
  23. package/.claude/nccgs/protocols/shared-workflow-context.md +33 -0
  24. package/.claude/nccgs/protocols/unity-boundary.md +20 -0
  25. package/.claude/nccgs/routing.generated.md +4 -4
  26. package/.claude/nccgs/settings.fragment.json +84 -24
  27. package/.claude/nccgs/studio.json +53 -53
  28. package/.claude/nccgs/tools/autonomy.mjs +13 -0
  29. package/.claude/nccgs/tools/command-tokens.mjs +22 -0
  30. package/.claude/nccgs/tools/hook-transport.mjs +34 -0
  31. package/.claude/nccgs/tools/native-completion.mjs +10 -1
  32. package/.claude/nccgs/tools/returned-handoff.mjs +11 -2
  33. package/.claude/nccgs/tools/shared-context-guard.mjs +60 -0
  34. package/.claude/nccgs/tools/shared-context.mjs +25 -0
  35. package/.claude/nccgs/tools/tool-guard.mjs +120 -131
  36. package/.claude/nccgs/tools/unity-transport.mjs +170 -0
  37. package/.claude/nccgs/workflow-catalog.json +4 -4
  38. package/CHANGELOG.md +235 -200
  39. package/CLAUDE.md +10 -8
  40. package/MEMORY.md +109 -60
  41. package/README.md +206 -191
  42. package/UPGRADING.md +130 -118
  43. package/VERSION +1 -1
  44. package/docs/ARCHITECTURE.md +131 -131
  45. package/docs/ASSET-PRODUCTION-GRAPHIFY-PLAN.md +230 -0
  46. package/docs/HUONG-DAN-MIGRATE-VA-SU-DUNG.md +117 -116
  47. package/docs/NCCGS-1.5.md +237 -237
  48. package/docs/PROJECT-POLICY.md +94 -94
  49. package/docs/RELEASE-STATUS.md +17 -9
  50. package/docs/UNINSTALL.md +3 -3
  51. package/docs/WFK17-HOOK-REPAIR.md +71 -0
  52. package/docs/WORKFLOWS.md +85 -85
  53. package/package.json +1 -1
  54. package/scaffold/.nccgs/project.yaml +109 -108
  55. package/scripts/benchmark-v15.mjs +173 -173
  56. package/scripts/doctor.mjs +261 -261
  57. package/scripts/install.mjs +530 -529
  58. package/scripts/run.mjs +5 -4
  59. package/scripts/supervise.mjs +4 -0
  60. package/scripts/uninstall.mjs +292 -292
  61. package/scripts/validate.mjs +285 -280
  62. package/tests/autonomy.test.mjs +126 -113
  63. package/tests/design-readiness.test.mjs +199 -199
  64. package/tests/fixtures/compiler-shutdown.cs +67 -64
  65. package/tests/fixtures/native-provider.mjs +61 -0
  66. package/tests/framework.test.mjs +740 -740
  67. package/tests/hook-repair.test.mjs +106 -0
  68. package/tests/lifecycle-v15.test.mjs +346 -300
  69. package/tests/native-completion-notices.test.mjs +181 -0
  70. package/tests/phase1.test.mjs +427 -416
  71. package/tests/phase2.test.mjs +250 -250
  72. package/tests/policy-v15.test.mjs +234 -234
  73. package/tests/returned-handoff-notices.test.mjs +170 -0
  74. package/tests/runtime-v15.test.mjs +1197 -1191
  75. package/tests/task-slices.test.mjs +285 -285
  76. 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
+ });