@punal_manalan/blender-project-harness 0.0.0-stage → 1.0.2

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 (91) hide show
  1. package/.kilo/agent/blender-grader.md +197 -0
  2. package/.kilo/agent/blender-worker.md +127 -0
  3. package/.kilo/blender-project-harness/config.example.json +71 -0
  4. package/.kilo/blender-project-harness/tools/harness/EXPECTED_FLOW.md +656 -0
  5. package/.kilo/blender-project-harness/tools/harness/blender-operations.mjs +882 -0
  6. package/.kilo/blender-project-harness/tools/harness/blender-operations.test.mjs +550 -0
  7. package/.kilo/blender-project-harness/tools/harness/blender.mjs +301 -0
  8. package/.kilo/blender-project-harness/tools/harness/blender.test.mjs +87 -0
  9. package/.kilo/blender-project-harness/tools/harness/cli.mjs +127 -0
  10. package/.kilo/blender-project-harness/tools/harness/documents.mjs +201 -0
  11. package/.kilo/blender-project-harness/tools/harness/download.mjs +94 -0
  12. package/.kilo/blender-project-harness/tools/harness/download.test.mjs +63 -0
  13. package/.kilo/blender-project-harness/tools/harness/engine.mjs +1866 -0
  14. package/.kilo/blender-project-harness/tools/harness/engine.test.mjs +2219 -0
  15. package/.kilo/blender-project-harness/tools/harness/intake.mjs +571 -0
  16. package/.kilo/blender-project-harness/tools/harness/intake.test.mjs +181 -0
  17. package/.kilo/blender-project-harness/tools/harness/package-lock.json +1227 -0
  18. package/.kilo/blender-project-harness/tools/harness/package.json +15 -0
  19. package/.kilo/blender-project-harness/tools/harness/plugin.mjs +550 -0
  20. package/.kilo/blender-project-harness/tools/harness/plugin.test.mjs +526 -0
  21. package/.kilo/blender-project-harness/tools/harness/proxy.mjs +222 -0
  22. package/.kilo/blender-project-harness/tools/harness/proxy.test.mjs +217 -0
  23. package/.kilo/blender-project-harness/tools/harness/scaffold.mjs +83 -0
  24. package/.kilo/blender-project-harness/tools/harness/scaffold.test.mjs +74 -0
  25. package/.kilo/blender-project-harness/tools/harness/server.mjs +24 -0
  26. package/.kilo/blender-project-harness/tools/harness/shell-policy.mjs +196 -0
  27. package/.kilo/blender-project-harness/tools/harness/shell-policy.test.mjs +68 -0
  28. package/.kilo/blender-project-harness/tools/harness/templates/project/Documents/ASSETS.md +29 -0
  29. package/.kilo/blender-project-harness/tools/harness/templates/project/Documents/DECISIONS.md +8 -0
  30. package/.kilo/blender-project-harness/tools/harness/templates/project/Documents/Goals/01-project-brief.md +71 -0
  31. package/.kilo/blender-project-harness/tools/harness/templates/project/Documents/Goals/README.md +17 -0
  32. package/.kilo/blender-project-harness/tools/harness/templates/project/Documents/INTAKE.md +8 -0
  33. package/.kilo/blender-project-harness/tools/harness/templates/project/Documents/README.md +57 -0
  34. package/.kilo/blender-project-harness/tools/harness/templates/project/Documents/REQUIREMENTS.md +64 -0
  35. package/.kilo/blender-project-harness/tools/harness/templates/project/Documents/REVIEW.md +29 -0
  36. package/.kilo/blender-project-harness/tools/harness/templates/project/Documents/References/README.md +18 -0
  37. package/.kilo/blender-project-harness/tools/harness/templates/project/Documents/Reviews/README.md +48 -0
  38. package/.kilo/blender-project-harness/tools/harness/templates/project/Documents/TASKSTRACKER.md +10 -0
  39. package/.kilo/blender-project-harness/tools/harness/templates/project/Documents/Tasks/README.md +4 -0
  40. package/.kilo/blender-project-harness/tools/harness/templates/project/Documents/WORKFLOW.md +36 -0
  41. package/.kilo/blender-project-harness/tools/harness/templates/project/README.md +130 -0
  42. package/.kilo/blender-project-harness/tools/harness/templates/project/assets/README.md +31 -0
  43. package/.kilo/blender-project-harness/tools/harness/templates/project/assets/audio/.gitkeep +0 -0
  44. package/.kilo/blender-project-harness/tools/harness/templates/project/assets/external/README.md +43 -0
  45. package/.kilo/blender-project-harness/tools/harness/templates/project/assets/external/SOURCE_TEMPLATE.md +18 -0
  46. package/.kilo/blender-project-harness/tools/harness/templates/project/assets/hdri/.gitkeep +0 -0
  47. package/.kilo/blender-project-harness/tools/harness/templates/project/assets/models/.gitkeep +0 -0
  48. package/.kilo/blender-project-harness/tools/harness/templates/project/assets/textures/.gitkeep +0 -0
  49. package/.kilo/blender-project-harness/tools/harness/templates/project/backups/.gitkeep +0 -0
  50. package/.kilo/blender-project-harness/tools/harness/templates/project/backups/README.md +5 -0
  51. package/.kilo/blender-project-harness/tools/harness/templates/project/exports/.gitkeep +0 -0
  52. package/.kilo/blender-project-harness/tools/harness/templates/project/exports/README.md +11 -0
  53. package/.kilo/blender-project-harness/tools/harness/templates/project/renders/README.md +7 -0
  54. package/.kilo/blender-project-harness/tools/harness/templates/project/renders/final/.gitkeep +0 -0
  55. package/.kilo/blender-project-harness/tools/harness/templates/project/renders/wip/.gitkeep +0 -0
  56. package/.kilo/blender-project-harness/tools/harness/templates/project/scenes/README.md +10 -0
  57. package/.kilo/blender-project-harness/tools/harness/templates/project/scripts/README.md +16 -0
  58. package/.kilo/blender-project-harness/tools/harness/test-fixture.mjs +91 -0
  59. package/.kilo/blender-project-harness/tools/harness/workspace.mjs +30 -0
  60. package/.kilo/blender-project-harness/tools/harness/workspace.test.mjs +31 -0
  61. package/.kilo/blender-project-harness/tools/test_blender_tools.py +427 -0
  62. package/.kilo/plugins/blender-workflow.js +3 -0
  63. package/.kilo/skills/blender-assets/SKILL.md +99 -0
  64. package/.kilo/skills/blender-mcp/SKILL.md +147 -0
  65. package/.kilo/skills/blender-mcp/references/README.md +22 -0
  66. package/.kilo/skills/blender-mcp/references/animation.md +83 -0
  67. package/.kilo/skills/blender-mcp/references/bpy.md +97 -0
  68. package/.kilo/skills/blender-mcp/references/level-design.md +53 -0
  69. package/.kilo/skills/blender-mcp/references/materials.md +80 -0
  70. package/.kilo/skills/blender-mcp/references/retopology.md +104 -0
  71. package/.kilo/skills/blender-mcp/references/rigging.md +123 -0
  72. package/.kilo/skills/blender-mcp/references/scene.md +72 -0
  73. package/.kilo/skills/blender-project-goal/SKILL.md +115 -0
  74. package/.kilo/skills/blender-project-grading/SKILL.md +184 -0
  75. package/.kilo/skills/blender-project-readme/SKILL.md +46 -0
  76. package/.kilo/skills/blender-project-requirements/SKILL.md +84 -0
  77. package/.kilo/skills/blender-project-scaffold/SKILL.md +59 -0
  78. package/.kilo/skills/blender-project-workflow/SKILL.md +96 -0
  79. package/.kilo/skills/blender-subagents/SKILL.md +204 -0
  80. package/.kilo/skills/blender-workflow-harness/SKILL.md +187 -0
  81. package/AGENTS.md +234 -0
  82. package/Blender/README.md +30 -0
  83. package/CHANGELOG.md +22 -0
  84. package/LICENSE +21 -0
  85. package/README.md +368 -2
  86. package/THIRD_PARTY_NOTICES.md +51 -0
  87. package/bin/blender-project-harness.mjs +4 -0
  88. package/installer/cli.mjs +116 -0
  89. package/installer/install.mjs +325 -0
  90. package/installer/install.test.mjs +134 -0
  91. package/package.json +58 -6
@@ -0,0 +1,181 @@
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 test from 'node:test';
6
+ import {
7
+ appendAssetRow,
8
+ appendIntakeRow,
9
+ appendReferenceRow,
10
+ classifyIntakeFile,
11
+ copyIntake,
12
+ createIntakeDocument,
13
+ planIntake,
14
+ sourceDocument,
15
+ } from './intake.mjs';
16
+
17
+ const PNG_1X1 = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Wl2ZQAAAABJRU5ErkJggg==', 'base64');
18
+ const GLB = Buffer.concat([Buffer.from('glTF'), Buffer.from([2, 0, 0, 0]), Buffer.alloc(8)]);
19
+
20
+ function workspace(t) {
21
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'kilo-intake-'));
22
+ t.after(() => fs.rmSync(root, { force: true, recursive: true }));
23
+ return root;
24
+ }
25
+
26
+ function write(root, name, content) {
27
+ const full = path.join(root, name);
28
+ fs.mkdirSync(path.dirname(full), { recursive: true });
29
+ fs.writeFileSync(full, content);
30
+ return full;
31
+ }
32
+
33
+ test('classifies only supported intake files', () => {
34
+ assert.equal(classifyIntakeFile('model.glb'), 'model');
35
+ assert.equal(classifyIntakeFile('model.blend'), 'model');
36
+ assert.equal(classifyIntakeFile('mood.png'), 'reference');
37
+ assert.equal(classifyIntakeFile('studio.exr'), 'hdri');
38
+ assert.equal(classifyIntakeFile('voice.wav'), 'audio');
39
+ assert.equal(classifyIntakeFile('build.py'), 'script');
40
+ assert.equal(classifyIntakeFile('notes.txt'), null);
41
+ assert.equal(classifyIntakeFile('archive.zip'), null);
42
+ });
43
+
44
+ test('records adjacent licence candidates without accepting them', (t) => {
45
+ const root = workspace(t);
46
+ const asset = write(root, 'vendor/model.glb', GLB);
47
+ write(root, 'vendor/LICENSE.txt', 'Example licence text\n');
48
+ const plan = planIntake({ root, projectName: 'Project', files: [asset], kind: 'model', source: { license: 'MIT', licenseText: 'MIT' } });
49
+ assert.deepEqual(plan.entries[0].licenseCandidates, ['LICENSE.txt']);
50
+ const source = sourceDocument({ entry: { ...plan.entries[0], stored: 'assets/external/vendor/model.glb' }, source: {}, license: '' });
51
+ assert.match(source, /Licence status: candidates only; not accepted/);
52
+ assert.match(source, /Adjacent licence candidates: LICENSE\.txt/);
53
+ });
54
+
55
+ test('plans generated references and copies them without treating them as assets', (t) => {
56
+ const root = workspace(t);
57
+ const source = write(root, 'downloads/mood board.png', PNG_1X1);
58
+ const projectRoot = path.join(root, 'project');
59
+ const plan = planIntake({
60
+ root,
61
+ projectName: 'Project',
62
+ files: ['downloads/mood board.png'],
63
+ generated: { tool: 'media-mcp', model: 'image-model', prompt: 'cozy room', seed: '42', width: 1024, height: 768, c2pa: true },
64
+ });
65
+ assert.equal(plan.entries.length, 1);
66
+ assert.equal(plan.entries[0].kind, 'reference');
67
+ assert.equal(plan.entries[0].external, false);
68
+ assert.equal(plan.entries[0].mime, 'image/png');
69
+ assert.deepEqual([plan.entries[0].width, plan.entries[0].height], [1, 1]);
70
+ assert.match(plan.entries[0].destination, /^Documents\/References\/mood-board-[a-f0-9]{8}\.png$/);
71
+ const result = copyIntake({ projectRoot, plan, licenseText: plan.licenseText });
72
+ assert.equal(result.copied.length, 1);
73
+ assert.ok(fs.existsSync(path.join(projectRoot, result.copied[0].stored)));
74
+ assert.equal(fs.existsSync(path.join(projectRoot, 'Documents', 'ASSETS.md')), false);
75
+ assert.equal(source, path.join(root, 'downloads', 'mood board.png'));
76
+ });
77
+
78
+ test('requires an explicit licence for external assets and writes its verbatim copy', (t) => {
79
+ const root = workspace(t);
80
+ const source = write(root, 'downloads/chair.glb', GLB);
81
+ const projectRoot = path.join(root, 'project');
82
+ const missingText = planIntake({
83
+ root,
84
+ projectName: 'Project',
85
+ files: [source],
86
+ kind: 'model',
87
+ source: { external: true, name: 'Desk chair', vendor: 'Example', license: 'CC0-1.0' },
88
+ });
89
+ assert.equal(missingText.entries.length, 0);
90
+ assert.match(missingText.rejected[0].reason, /licenseText|licenseFile/);
91
+ const plan = planIntake({
92
+ root,
93
+ projectName: 'Project',
94
+ files: [source],
95
+ kind: 'model',
96
+ source: { external: true, name: 'Desk chair', vendor: 'Example', author: 'Example', license: 'CC0-1.0', licenseText: 'CC0 text' },
97
+ });
98
+ assert.equal(plan.entries[0].destination, 'assets/external/Example/Desk-chair/chair.glb');
99
+ const result = copyIntake({ projectRoot, plan, licenseText: plan.licenseText });
100
+ assert.equal(result.copied.length, 1);
101
+ const license = path.join(projectRoot, 'assets', 'external', 'Example', 'Desk-chair', 'LICENSE.txt');
102
+ assert.equal(fs.readFileSync(license, 'utf8'), 'CC0 text');
103
+ });
104
+
105
+ test('deduplicates identical content and suffixes name collisions', (t) => {
106
+ const root = workspace(t);
107
+ const source = write(root, 'downloads/ref.png', PNG_1X1);
108
+ const projectRoot = path.join(root, 'project');
109
+ const plan = planIntake({ root, projectName: 'Project', files: [source], kind: 'reference' });
110
+ const first = copyIntake({ projectRoot, plan, licenseText: plan.licenseText });
111
+ const second = copyIntake({ projectRoot, plan, licenseText: plan.licenseText });
112
+ assert.equal(first.copied.length, 1);
113
+ assert.equal(second.copied.length, 0);
114
+ assert.equal(second.duplicates.length, 1);
115
+ fs.writeFileSync(first.copied[0].target, 'different');
116
+ const third = copyIntake({ projectRoot, plan, licenseText: plan.licenseText });
117
+ assert.equal(third.copied.length, 1);
118
+ assert.match(third.copied[0].stored, /-2\.png$/);
119
+ });
120
+
121
+ test('rejects files whose signature does not match the extension', (t) => {
122
+ const root = workspace(t);
123
+ const fake = write(root, 'downloads/fake.png', Buffer.from('not a png'));
124
+ const plan = planIntake({ root, projectName: 'Project', files: [fake] });
125
+ assert.equal(plan.entries.length, 0);
126
+ assert.match(plan.rejected[0].reason, /signature does not match PNG/);
127
+ });
128
+
129
+ test('rejects unsupported files and symlinks without copying', (t) => {
130
+ const root = workspace(t);
131
+ const unsupported = write(root, 'downloads/notes.txt', 'notes');
132
+ const plan = planIntake({ root, projectName: 'Project', files: [unsupported] });
133
+ assert.equal(plan.entries.length, 0);
134
+ assert.match(plan.rejected[0].reason, /unsupported/);
135
+ if (process.platform !== 'win32') {
136
+ const linked = path.join(root, 'downloads', 'linked.png');
137
+ fs.symlinkSync(unsupported, linked);
138
+ const linkedPlan = planIntake({ root, projectName: 'Project', files: [linked] });
139
+ assert.equal(linkedPlan.entries.length, 0);
140
+ assert.match(linkedPlan.rejected[0].reason, /symlink/);
141
+ }
142
+ });
143
+
144
+ test('project-owned models bypass third-party metadata only when declared', (t) => {
145
+ const root = workspace(t);
146
+ const model = write(root, 'downloads/chair.glb', GLB);
147
+ const projectOwned = planIntake({ root, projectName: 'Project', files: [model], kind: 'model', source: { external: false } });
148
+ assert.equal(projectOwned.entries[0].destination, 'assets/models/chair.glb');
149
+ const external = planIntake({ root, projectName: 'Project', files: [model], kind: 'model' });
150
+ assert.equal(external.entries.length, 0);
151
+ assert.match(external.rejected[0].reason, /source.license/);
152
+ });
153
+
154
+ test('accepts images as textures and scripts only as source', (t) => {
155
+ const root = workspace(t);
156
+ const image = write(root, 'downloads/wood.png', PNG_1X1);
157
+ const script = write(root, 'downloads/build.py', 'print("hello")\n');
158
+ const texture = planIntake({ root, projectName: 'Project', files: [image], kind: 'texture' });
159
+ assert.equal(texture.entries[0].kind, 'texture');
160
+ assert.equal(texture.entries[0].destination, 'assets/textures/wood.png');
161
+ const source = planIntake({ root, projectName: 'Project', files: [script], kind: 'script' });
162
+ assert.equal(source.entries[0].kind, 'script');
163
+ assert.equal(source.entries[0].destination, 'scripts/build.py');
164
+ });
165
+
166
+ test('document helpers create valid tables without duplicating paths', () => {
167
+ const assets = '# Assets\n\n## Rules\n\nRules.\n';
168
+ const withAsset = appendAssetRow(assets, {
169
+ asset: 'Chair', sourceUrl: 'https://example.invalid', author: 'Example', license: 'CC0-1.0',
170
+ localPath: 'assets/external/Example/Chair/chair.glb', packaging: 'packed', redistributable: 'yes', notes: '',
171
+ });
172
+ assert.match(withAsset, /## Rules/);
173
+ assert.match(withAsset, /Chair/);
174
+ assert.equal(appendAssetRow(withAsset, {
175
+ asset: 'Chair', sourceUrl: '', author: '', license: '', localPath: 'assets/external/Example/Chair/chair.glb', packaging: '', redistributable: '', notes: '',
176
+ }), withAsset);
177
+ const references = '# References\n\n## Index\n\n| Reference | Source | What it informs | Notes |\n|---|---|---|---|\n';
178
+ assert.match(appendReferenceRow(references, { reference: 'mood.png', source: 'user', informs: 'mood', notes: '' }), /mood\.png/);
179
+ const intake = createIntakeDocument('Project');
180
+ assert.match(appendIntakeRow(intake, { time: 'now', kind: 'reference', stored: 'Documents/References/mood.png', sha256: 'a', source: 'user', license: '', status: 'copied' }), /mood\.png/);
181
+ });