nccgs 2.0.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 (82) 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 +9 -3
  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/compile-policy.mjs +4 -0
  31. package/.claude/nccgs/tools/configure-models.mjs +4 -0
  32. package/.claude/nccgs/tools/hook-transport.mjs +34 -0
  33. package/.claude/nccgs/tools/installation-guard.mjs +55 -0
  34. package/.claude/nccgs/tools/native-completion.mjs +10 -1
  35. package/.claude/nccgs/tools/returned-handoff.mjs +11 -2
  36. package/.claude/nccgs/tools/shared-context-guard.mjs +60 -0
  37. package/.claude/nccgs/tools/shared-context.mjs +25 -0
  38. package/.claude/nccgs/tools/tool-guard.mjs +120 -131
  39. package/.claude/nccgs/tools/unity-transport.mjs +170 -0
  40. package/.claude/nccgs/workflow-catalog.json +4 -4
  41. package/CHANGELOG.md +235 -165
  42. package/CLAUDE.md +10 -8
  43. package/MEMORY.md +109 -56
  44. package/README.md +206 -168
  45. package/UPGRADING.md +130 -112
  46. package/VERSION +1 -1
  47. package/docs/ARCHITECTURE.md +131 -131
  48. package/docs/ASSET-PRODUCTION-GRAPHIFY-PLAN.md +230 -0
  49. package/docs/HUONG-DAN-MIGRATE-VA-SU-DUNG.md +117 -87
  50. package/docs/NCCGS-1.5.md +237 -237
  51. package/docs/PROJECT-POLICY.md +94 -94
  52. package/docs/RELEASE-STATUS.md +32 -7
  53. package/docs/UNINSTALL.md +134 -0
  54. package/docs/WFK17-HOOK-REPAIR.md +71 -0
  55. package/docs/WORKFLOWS.md +85 -85
  56. package/package.json +1 -1
  57. package/scaffold/.nccgs/project.yaml +109 -108
  58. package/scripts/benchmark-v15.mjs +173 -173
  59. package/scripts/cli.mjs +11 -2
  60. package/scripts/doctor.mjs +261 -261
  61. package/scripts/install.mjs +530 -515
  62. package/scripts/recover-session.mjs +89 -0
  63. package/scripts/run.mjs +22 -9
  64. package/scripts/supervise.mjs +4 -0
  65. package/scripts/uninstall.mjs +292 -0
  66. package/scripts/validate.mjs +285 -278
  67. package/tests/autonomy.test.mjs +126 -113
  68. package/tests/design-readiness.test.mjs +199 -199
  69. package/tests/fixtures/compiler-shutdown.cs +67 -64
  70. package/tests/fixtures/native-provider.mjs +61 -0
  71. package/tests/framework.test.mjs +740 -740
  72. package/tests/hook-repair.test.mjs +106 -0
  73. package/tests/lifecycle-v15.test.mjs +346 -300
  74. package/tests/native-completion-notices.test.mjs +181 -0
  75. package/tests/phase1.test.mjs +427 -416
  76. package/tests/phase2.test.mjs +250 -250
  77. package/tests/policy-v15.test.mjs +234 -234
  78. package/tests/returned-handoff-notices.test.mjs +170 -0
  79. package/tests/runtime-v15.test.mjs +1197 -1191
  80. package/tests/task-slices.test.mjs +285 -285
  81. package/tests/uninstall.test.mjs +477 -0
  82. 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
+ });