harness-everything 0.36.2 → 0.36.3

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.
@@ -10,7 +10,7 @@
10
10
  "name": "harness-everything",
11
11
  "source": "./",
12
12
  "description": "Harness OS: an orchestrated agent operating system — tier routing, circuit breaker guardrails, verification gates, WAL session memory, and self-evolving workspace rules.",
13
- "version": "0.36.2",
13
+ "version": "0.36.3",
14
14
  "author": {
15
15
  "name": "Miya Daniel"
16
16
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "harness-everything",
3
3
  "description": "Harness OS: an orchestrated agent operating system — tier routing, circuit breaker guardrails, verification gates, WAL session memory, and self-evolving workspace rules.",
4
- "version": "0.36.2",
4
+ "version": "0.36.3",
5
5
  "author": {
6
6
  "name": "Miya Daniel"
7
7
  },
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## [0.36.3](https://github.com/dyphn1/Harness-everything/compare/v0.36.2...v0.36.3) (2026-10-20)
6
+
7
+ ### Bug Fixes
8
+
9
+ * **project-docs:** reject non-string required fields and unsafe init paths ([#331](https://github.com/dyphn1/Harness-everything/issues/331)) ([685df07](https://github.com/dyphn1/Harness-everything/commit/685df076a84a7c336a4ce3c9e06f97ebae2bd8b8))
10
+ * **project-docs:** stop host-only init from overwriting malformed manifests ([#331](https://github.com/dyphn1/Harness-everything/issues/331)) ([93df52c](https://github.com/dyphn1/Harness-everything/commit/93df52c94f2f55df828a0176fb7bf533c412fc6c))
11
+
5
12
  ## [0.36.2](https://github.com/dyphn1/Harness-everything/compare/v0.36.1...v0.36.2) (2026-10-20)
6
13
 
7
14
  ### Bug Fixes
@@ -8,6 +8,14 @@ const resolverPath = path.join(__dirname, '..', 'multi-agent-workspace', 'script
8
8
  const toSpecResolverPath = path.join(__dirname, '..', 'to-spec', 'scripts', 'project-docs-resolver.js');
9
9
  const harnessResolverPath = path.join(__dirname, '..', 'harness-everything', 'scripts', 'project-docs-resolver.js');
10
10
  const toSpecCheckPath = path.join(__dirname, '..', 'to-spec', 'scripts', 'check-project-docs.js');
11
+ const packagedToSpecCheckPath = path.join(__dirname, '..', 'plugins', 'harness-everything', 'skills', 'to-spec', 'scripts', 'check-project-docs.js');
12
+ const packagedToSpecResolverPath = path.join(__dirname, '..', 'plugins', 'harness-everything', 'skills', 'to-spec', 'scripts', 'project-docs-resolver.js');
13
+ const packagedMultiAgentResolverPath = path.join(__dirname, '..', 'plugins', 'harness-everything', 'skills', 'multi-agent-workspace', 'scripts', 'project-docs-resolver.js');
14
+ const packagedHarnessResolverPath = path.join(__dirname, '..', 'plugins', 'harness-everything', 'skills', 'harness-everything', 'scripts', 'project-docs-resolver.js');
15
+ const toSpecCliLegs = Object.freeze([
16
+ ['source', toSpecCheckPath],
17
+ ['packaged', packagedToSpecCheckPath]
18
+ ]);
11
19
  const resolver = require(resolverPath);
12
20
  const toSpecResolver = require(toSpecResolverPath);
13
21
  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-2aw-project-docs-'));
@@ -342,8 +350,12 @@ checkCase('2aw. project file zone fields drive existing zone resolution', () =>
342
350
  checkCase('2aw. to-spec ships the same standalone resolver implementation', () => {
343
351
  const source = fs.readFileSync(toSpecResolverPath, 'utf8');
344
352
  assert.strictEqual(source, fs.readFileSync(resolverPath, 'utf8'));
353
+ assert.strictEqual(source, fs.readFileSync(packagedToSpecResolverPath, 'utf8'));
354
+ assert.strictEqual(source, fs.readFileSync(packagedMultiAgentResolverPath, 'utf8'));
355
+ assert.strictEqual(source, fs.readFileSync(packagedHarnessResolverPath, 'utf8'));
345
356
  assert.ok(!source.includes("require('../../multi-agent-workspace"), 'standalone resolver must not defer to a sibling skill');
346
357
  assert.strictEqual(typeof toSpecResolver.resolveDocsConfig, 'function');
358
+ assert.strictEqual(typeof toSpecResolver.normalizeHostProjectDocs, 'function');
347
359
  });
348
360
 
349
361
  checkCase('2aw dogfood. harness-everything ships the byte-identical standalone resolver', () => {
@@ -628,12 +640,12 @@ checkCase('2aw owner-p2. init rejects whitespace-only required values consistent
628
640
  assert.strictEqual(fs.readFileSync(hostFile2, 'utf8'), host2Before, 'whitespace-only host init must write nothing');
629
641
  });
630
642
 
631
- function runToSpecCheck(root) {
632
- return spawnSync(process.execPath, [toSpecCheckPath, 'check', '--workspace', root], { encoding: 'utf8' });
643
+ function runToSpecCheck(root, scriptPath = toSpecCheckPath) {
644
+ return spawnSync(process.execPath, [scriptPath, 'check', '--workspace', root], { encoding: 'utf8' });
633
645
  }
634
646
 
635
- function runToSpecInit(root, args) {
636
- return spawnSync(process.execPath, [toSpecCheckPath, 'init', '--workspace', root, ...args], { encoding: 'utf8' });
647
+ function runToSpecInit(root, args, scriptPath = toSpecCheckPath) {
648
+ return spawnSync(process.execPath, [scriptPath, 'init', '--workspace', root, ...args], { encoding: 'utf8' });
637
649
  }
638
650
 
639
651
  checkCase('2aw owner-p2. init fills only missing fields in an existing valid project file and preserves the rest', () => {
@@ -701,24 +713,188 @@ checkCase('2aw owner-p2. init leaves malformed or schema-invalid project files u
701
713
  assert.strictEqual(fs.readFileSync(invalidFile, 'utf8'), invalidBefore, 'invalid schema file untouched');
702
714
  });
703
715
 
716
+ checkCase('2aw issue331. host-only init rejects malformed JSON without losing a registered custom skill', () => {
717
+ const malformed = '{\n "package": "harness-everything",\n "skills": [{"id":"custom-skill","dirPath":"custom-skill"}],\n "agents": [],\n "projectDocs": {"governed": [';
718
+ for (const [leg, scriptPath] of toSpecCliLegs) {
719
+ const root = workspace(`issue331-${leg}-host-init-malformed`);
720
+ const hostFile = writeHostConfig(root, 'claude', null, malformed);
721
+ const before = fs.readFileSync(hostFile);
722
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
723
+ assert.strictEqual(result.status, 1, `${leg} malformed host manifest must fail init: ${result.stdout}${result.stderr}`);
724
+ assert.match(`${result.stdout}\n${result.stderr}`, /\.claude\/harness-everything\/manifest\.json/);
725
+ assert.match(`${result.stdout}\n${result.stderr}`, /invalid JSON/i);
726
+ assert.match(`${result.stdout}\n${result.stderr}`, /repair|fix/i);
727
+ assert.deepStrictEqual(fs.readFileSync(hostFile), before, 'malformed host manifest stays byte-identical');
728
+ }
729
+ });
730
+
731
+ checkCase('2aw issue331. host-only init rejects schema-invalid projectDocs without writing', () => {
732
+ for (const [leg, scriptPath] of toSpecCliLegs) {
733
+ const root = workspace(`issue331-${leg}-host-init-invalid-schema`);
734
+ const hostFile = writeHostConfig(root, 'claude', { governed: 'not-an-array', domains: ['GRPH'] });
735
+ const before = fs.readFileSync(hostFile);
736
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
737
+ assert.strictEqual(result.status, 1, `${leg} schema-invalid host projectDocs must fail init: ${result.stdout}${result.stderr}`);
738
+ assert.match(`${result.stdout}\n${result.stderr}`, /\.claude\/harness-everything\/manifest\.json/);
739
+ assert.match(`${result.stdout}\n${result.stderr}`, /invalid-governed/);
740
+ assert.match(`${result.stdout}\n${result.stderr}`, /repair|fix/i);
741
+ assert.deepStrictEqual(fs.readFileSync(hostFile), before, 'schema-invalid host manifest stays byte-identical');
742
+ }
743
+ });
744
+
745
+ for (const [leg, scriptPath] of toSpecCliLegs) {
746
+ checkCase(`2aw issue331. ${leg} host-only init accepts legacy string domains without changing its on-disk value`, () => {
747
+ const root = workspace(`issue331-${leg}-legacy-host-domains`);
748
+ const hostFile = writeHostConfig(root, 'claude', {
749
+ domains: 'records/domain',
750
+ governed: [{ path: 'docs/prd/**', role: 'prd' }]
751
+ });
752
+ const before = JSON.parse(fs.readFileSync(hostFile, 'utf8'));
753
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
754
+ assert.strictEqual(result.status, 0, `${result.stdout}\n${result.stderr}`);
755
+ const after = JSON.parse(fs.readFileSync(hostFile, 'utf8'));
756
+ assert.deepStrictEqual(after.projectDocs.domains, before.projectDocs.domains, 'deprecated string domains remains unchanged on disk');
757
+ assert.strictEqual(after.projectDocs.docLocation, 'docs/');
758
+ assert.strictEqual(after.projectDocs.tracker, 'GitHub Issues');
759
+ assert.strictEqual(after.projectDocs.issueDefinition, 'Title and acceptance');
760
+ const check = runToSpecCheck(root, scriptPath);
761
+ assert.strictEqual(check.status, 0, `check accepts host legacy value after init: ${check.stdout}\n${check.stderr}`);
762
+ });
763
+
764
+ checkCase(`2aw issue331. ${leg} complete legacy host permits a no-op init`, () => {
765
+ const root = workspace(`issue331-${leg}-legacy-host-complete`);
766
+ const hostFile = writeHostConfig(root, 'claude', {
767
+ domains: 'records/domain',
768
+ docLocation: 'docs/', tracker: 'GitHub Issues', issueDefinition: 'Title and acceptance'
769
+ });
770
+ const before = fs.readFileSync(hostFile);
771
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
772
+ assert.strictEqual(result.status, 0, `${result.stdout}\n${result.stderr}`);
773
+ assert.deepStrictEqual(fs.readFileSync(hostFile), before, 'complete host config remains byte-identical');
774
+ });
775
+
776
+ checkCase(`2aw issue331. ${leg} directory manifest reports a read failure and leaves contents intact`, () => {
777
+ const root = workspace(`issue331-${leg}-directory-manifest`);
778
+ const manifestPath = path.join(root, '.claude', 'harness-everything', 'manifest.json');
779
+ fs.mkdirSync(manifestPath, { recursive: true });
780
+ const marker = path.join(manifestPath, 'registration.json');
781
+ fs.writeFileSync(marker, '{"skills":[{"id":"custom-skill"}]}', 'utf8');
782
+ const before = fs.readFileSync(marker);
783
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
784
+ const output = `${result.stdout}\n${result.stderr}`;
785
+ assert.strictEqual(result.status, 1, `directory manifest must fail init: ${output}`);
786
+ assert.match(output, /\.claude\/harness-everything\/manifest\.json/);
787
+ assert.match(output, /cannot be read/i);
788
+ assert.doesNotMatch(output, /invalid JSON/i);
789
+ assert.match(output, /repair|restore|readable/i);
790
+ assert.deepStrictEqual(fs.readFileSync(marker), before, 'directory contents remain byte-identical');
791
+ });
792
+ }
793
+
794
+ checkCase('2aw issue331. modern array domains remain accepted on source and packaged CLIs', () => {
795
+ for (const [leg, scriptPath] of toSpecCliLegs) {
796
+ const root = workspace(`issue331-${leg}-modern-host-domains`);
797
+ const hostFile = writeHostConfig(root, 'claude', {
798
+ domains: ['GRPH'], governed: [{ path: 'docs/prd/**', role: 'prd' }]
799
+ });
800
+ const before = JSON.parse(fs.readFileSync(hostFile, 'utf8'));
801
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
802
+ assert.strictEqual(result.status, 0, `${leg}: ${result.stdout}\n${result.stderr}`);
803
+ const after = JSON.parse(fs.readFileSync(hostFile, 'utf8'));
804
+ assert.deepStrictEqual(after.projectDocs.domains, before.projectDocs.domains, 'modern domains array is preserved');
805
+ assert.strictEqual(runToSpecCheck(root, scriptPath).status, 0, 'modern host config passes check after init');
806
+ }
807
+ });
808
+
809
+ checkCase('2aw issue331. project-file domains string remains invalid on source and packaged CLIs', () => {
810
+ for (const [leg, scriptPath] of toSpecCliLegs) {
811
+ const root = workspace(`issue331-${leg}-project-domains-string`);
812
+ const projectFile = writeProjectConfig(root, {
813
+ governed: [{ path: 'docs/prd/**', role: 'prd' }],
814
+ domains: 'records/domain'
815
+ });
816
+ const before = fs.readFileSync(projectFile);
817
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
818
+ const output = `${result.stdout}\n${result.stderr}`;
819
+ assert.strictEqual(result.status, 1, `${leg} project-file legacy value must remain invalid: ${output}`);
820
+ assert.match(output, /invalid-domains/);
821
+ assert.deepStrictEqual(fs.readFileSync(projectFile), before, 'invalid project file remains byte-identical');
822
+ }
823
+ });
824
+
825
+ checkCase('2aw issue331. source and packaged host validators still reject malformed governed globs without writes', () => {
826
+ for (const [leg, scriptPath] of toSpecCliLegs) {
827
+ const root = workspace(`issue331-${leg}-invalid-host-glob`);
828
+ const hostFile = writeHostConfig(root, 'claude', {
829
+ governed: [{ path: 'docs//prd/**', role: 'prd' }], domains: ['GRPH']
830
+ });
831
+ const before = fs.readFileSync(hostFile);
832
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
833
+ const output = `${result.stdout}\n${result.stderr}`;
834
+ assert.strictEqual(result.status, 1, `${leg} malformed host glob must fail: ${output}`);
835
+ assert.match(output, /invalid-governed-path|failed schema validation/i);
836
+ assert.deepStrictEqual(fs.readFileSync(hostFile), before, 'invalid host config remains byte-identical');
837
+ }
838
+ });
839
+
840
+ checkCase('2aw issue331. malformed host aborts a multi-host init before any manifest changes', () => {
841
+ for (const [leg, scriptPath] of toSpecCliLegs) {
842
+ const root = workspace(`issue331-${leg}-host-init-multi-host-atomic`);
843
+ const validHost = writeHostConfig(root, 'claude', {
844
+ governed: [{ path: 'docs/prd/**', role: 'prd' }], domains: ['GRPH'], docLocation: 'host-docs/'
845
+ });
846
+ const malformedHost = writeHostConfig(root, 'codex', null, '{"skills":[{"id":"custom-skill"}], broken');
847
+ const validBefore = fs.readFileSync(validHost);
848
+ const malformedBefore = fs.readFileSync(malformedHost);
849
+ const result = runToSpecInit(root, ['--doc-location', 'host-docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
850
+ assert.strictEqual(result.status, 1, `${leg} malformed host must abort the full plan: ${result.stdout}${result.stderr}`);
851
+ assert.match(`${result.stdout}\n${result.stderr}`, /\.codex\/harness-everything\/manifest\.json/);
852
+ assert.deepStrictEqual(fs.readFileSync(validHost), validBefore, 'valid host is not written before malformed peer is rejected');
853
+ assert.deepStrictEqual(fs.readFileSync(malformedHost), malformedBefore, 'malformed host remains byte-identical');
854
+ }
855
+ });
856
+
857
+ checkCase('2aw issue331. absent host manifest still receives the requested projectDocs fields', () => {
858
+ for (const [leg, scriptPath] of toSpecCliLegs) {
859
+ const root = workspace(`issue331-${leg}-host-init-absent`);
860
+ const hostDir = path.join(root, '.claude', 'harness-everything');
861
+ fs.mkdirSync(hostDir, { recursive: true });
862
+ const hostFile = path.join(hostDir, 'manifest.json');
863
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
864
+ assert.strictEqual(result.status, 0, `${leg}: ${result.stdout}\n${result.stderr}`);
865
+ const created = JSON.parse(fs.readFileSync(hostFile, 'utf8'));
866
+ assert.deepStrictEqual(created.projectDocs.docLocation, 'docs/');
867
+ assert.strictEqual(created.projectDocs.tracker, 'GitHub Issues');
868
+ assert.strictEqual(created.projectDocs.issueDefinition, 'Title and acceptance');
869
+ }
870
+ });
871
+
704
872
  checkCase('2aw owner-p2. host-only init preserves existing host projectDocs keys and fills only missing fields', () => {
705
873
  const root = workspace('owner-p2-init-host-preserve');
706
- const hostFile = writeHostConfig(root, 'claude', {
707
- governed: [{ path: 'docs/prd/**', role: 'prd' }],
708
- domains: ['GRPH'],
709
- docLocation: 'host-docs/',
710
- updatedAt: '2026-01-01T00:00:00.000Z'
711
- });
874
+ const hostFile = path.join(root, '.claude', 'harness-everything', 'manifest.json');
875
+ fs.mkdirSync(path.dirname(hostFile), { recursive: true });
876
+ const registration = { id: 'custom-skill', dirPath: 'custom-skill', installedAt: '2026-01-01T00:00:00.000Z' };
877
+ fs.writeFileSync(hostFile, `${JSON.stringify({
878
+ package: 'harness-everything', skills: [registration], agents: [], projectDocs: {
879
+ governed: [{ path: 'docs/prd/**', role: 'prd' }],
880
+ domains: ['GRPH'],
881
+ docLocation: 'host-docs/',
882
+ updatedAt: '2026-01-01T00:00:00.000Z'
883
+ }
884
+ }, null, 2)}\n`, 'utf8');
712
885
  const before = JSON.parse(fs.readFileSync(hostFile, 'utf8'));
713
886
  const result = runToSpecInit(root, ['--doc-location', 'host-docs/', '--tracker', 'host-tracker', '--issue-definition', 'host-template']);
714
887
  assert.strictEqual(result.status, 0, `${result.stdout}\n${result.stderr}`);
715
888
  const after = JSON.parse(fs.readFileSync(hostFile, 'utf8'));
889
+ assert.deepStrictEqual(after.skills, before.skills, 'custom skill registrations preserved');
716
890
  assert.deepStrictEqual(after.projectDocs.governed, before.projectDocs.governed, 'host governed governance keys preserved');
717
891
  assert.deepStrictEqual(after.projectDocs.domains, before.projectDocs.domains, 'host domains preserved');
718
892
  assert.strictEqual(after.projectDocs.docLocation, 'host-docs/', 'configured docLocation preserved');
719
893
  assert.strictEqual(after.projectDocs.tracker, 'host-tracker', 'missing tracker filled');
720
894
  assert.strictEqual(after.projectDocs.issueDefinition, 'host-template', 'missing issueDefinition filled');
721
895
  assert.strictEqual(after.projectDocs.updatedAt, before.projectDocs.updatedAt, 'existing updatedAt preserved');
896
+ const check = runToSpecCheck(root);
897
+ assert.strictEqual(check.status, 0, `check succeeds after host-only init: ${check.stdout}\n${check.stderr}`);
722
898
  const conflicting = runToSpecInit(root, ['--doc-location', 'host-docs/', '--tracker', 'different-tracker', '--issue-definition', 'host-template']);
723
899
  assert.strictEqual(conflicting.status, 1, `conflicting host flag must fail: ${conflicting.stdout}${conflicting.stderr}`);
724
900
  assert.match(`${conflicting.stdout}\n${conflicting.stderr}`, /tracker/);
@@ -756,4 +932,268 @@ checkCase('2aw fix. to-spec CLI refuses conflicting hosts and invalid project ov
756
932
  assert.ok(!rejected.stdout.includes('host-docs'));
757
933
  });
758
934
 
935
+ for (const [leg, scriptPath] of toSpecCliLegs) {
936
+ for (const [field, value] of [['docLocation', 42], ['tracker', {}], ['issueDefinition', []]]) {
937
+ checkCase(`2aw issue331 p2. ${leg} host non-string ${field} fails init without writing the manifest`, () => {
938
+ const root = workspace(`issue331-p2-${leg}-type-${field}`);
939
+ const hostConfig = { docLocation: 'docs/', tracker: 'GitHub Issues', issueDefinition: 'Title and acceptance' };
940
+ hostConfig[field] = value;
941
+ const hostFile = writeHostConfig(root, 'claude', hostConfig);
942
+ const before = fs.readFileSync(hostFile);
943
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
944
+ const output = `${result.stdout}\n${result.stderr}`;
945
+ assert.strictEqual(result.status, 1, `${leg} non-string host ${field} must fail init: ${output}`);
946
+ assert.match(output, /\.claude\/harness-everything\/manifest\.json/);
947
+ assert.match(output, new RegExp(field));
948
+ assert.deepStrictEqual(fs.readFileSync(hostFile), before, `non-string host ${field} manifest stays byte-identical`);
949
+ });
950
+ }
951
+
952
+ checkCase(`2aw issue331 p2. ${leg} multi-host init rejects a non-string peer and leaves both manifests byte-identical`, () => {
953
+ const root = workspace(`issue331-p2-${leg}-multi-host`);
954
+ const validFile = writeHostConfig(root, 'claude', { governed: [{ path: 'docs/prd/**', role: 'prd' }], domains: ['GRPH'] });
955
+ const invalidFile = writeHostConfig(root, 'codex', { docLocation: 42 });
956
+ const validBefore = fs.readFileSync(validFile);
957
+ const invalidBefore = fs.readFileSync(invalidFile);
958
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
959
+ const output = `${result.stdout}\n${result.stderr}`;
960
+ assert.strictEqual(result.status, 1, `${leg} non-string codex docLocation must abort the full plan: ${output}`);
961
+ assert.match(output, /\.codex\/harness-everything\/manifest\.json/);
962
+ assert.deepStrictEqual(fs.readFileSync(validFile), validBefore, 'valid claude host is not written before the non-string peer is rejected');
963
+ assert.deepStrictEqual(fs.readFileSync(invalidFile), invalidBefore, 'non-string codex host stays byte-identical');
964
+ });
965
+
966
+ checkCase(`2aw issue331 p2. ${leg} init still fills absent and empty-string host fields`, () => {
967
+ const root = workspace(`issue331-p2-${leg}-empty-control`);
968
+ const hostFile = writeHostConfig(root, 'claude', { docLocation: '', tracker: ' ', governed: [{ path: 'docs/prd/**', role: 'prd' }] });
969
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
970
+ assert.strictEqual(result.status, 0, `${leg}: ${result.stdout}\n${result.stderr}`);
971
+ const after = JSON.parse(fs.readFileSync(hostFile, 'utf8'));
972
+ assert.strictEqual(after.projectDocs.docLocation, 'docs/', 'empty-string docLocation filled');
973
+ assert.strictEqual(after.projectDocs.tracker, 'GitHub Issues', 'whitespace-only tracker filled');
974
+ assert.strictEqual(after.projectDocs.issueDefinition, 'Title and acceptance', 'absent issueDefinition filled');
975
+ assert.deepStrictEqual(after.projectDocs.governed, [{ path: 'docs/prd/**', role: 'prd' }], 'governed preserved');
976
+ });
977
+
978
+ checkCase(`2aw issue331 p2. ${leg} dangling manifest symlink to a target outside the workspace fails init without creating it`, () => {
979
+ const root = workspace(`issue331-p2-${leg}-dangling-link`);
980
+ const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-2aw-p2-outside-'));
981
+ try {
982
+ const target = path.join(outside, 'escaped-manifest.json');
983
+ const hostDir = path.join(root, '.claude', 'harness-everything');
984
+ fs.mkdirSync(hostDir, { recursive: true });
985
+ const linkFile = path.join(hostDir, 'manifest.json');
986
+ try {
987
+ fs.symlinkSync(target, linkFile);
988
+ } catch (error) {
989
+ if (error.code === 'EPERM') {
990
+ console.log(` SKIP ${leg} dangling-symlink case: symlink creation is not permitted on this host (${error.code})`);
991
+ return;
992
+ }
993
+ throw error;
994
+ }
995
+ const validFile = writeHostConfig(root, 'codex', { governed: [{ path: 'docs/prd/**', role: 'prd' }], domains: ['GRPH'] });
996
+ const validBefore = fs.readFileSync(validFile);
997
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
998
+ const output = `${result.stdout}\n${result.stderr}`;
999
+ assert.strictEqual(result.status, 1, `${leg} dangling manifest symlink must fail init: ${output}`);
1000
+ assert.match(output, /\.claude\/harness-everything\/manifest\.json/);
1001
+ assert.ok(!fs.existsSync(target), 'symlink target outside the workspace must not be created');
1002
+ assert.ok(fs.lstatSync(linkFile).isSymbolicLink(), 'manifest path must remain a symlink');
1003
+ assert.strictEqual(fs.readlinkSync(linkFile), target, 'symlink must still point at its original target');
1004
+ assert.ok(!fs.existsSync(linkFile), 'symlink must remain dangling');
1005
+ assert.deepStrictEqual(fs.readFileSync(validFile), validBefore, 'second valid host manifest stays byte-identical');
1006
+ } finally {
1007
+ fs.rmSync(outside, { recursive: true, force: true });
1008
+ }
1009
+ });
1010
+ }
1011
+
1012
+ function trySymlink(target, linkPath, label, type) {
1013
+ try {
1014
+ fs.symlinkSync(target, linkPath, type);
1015
+ return true;
1016
+ } catch (error) {
1017
+ if (error.code !== 'EPERM') throw error;
1018
+ console.log(` SKIP ${label}: symlink creation is not permitted on this host (${error.code})`);
1019
+ return false;
1020
+ }
1021
+ }
1022
+
1023
+ for (const [leg, scriptPath] of toSpecCliLegs) {
1024
+ checkCase(`2aw issue331 p2b. ${leg} host null docLocation fails init without writing the manifest`, () => {
1025
+ const root = workspace(`issue331-p2b-${leg}-null-doc-location`);
1026
+ const hostFile = writeHostConfig(root, 'claude', { docLocation: null, tracker: 'GitHub Issues', issueDefinition: 'Title and acceptance' });
1027
+ const before = fs.readFileSync(hostFile);
1028
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
1029
+ const output = `${result.stdout}\n${result.stderr}`;
1030
+ assert.strictEqual(result.status, 1, `${leg} null host docLocation must fail init: ${output}`);
1031
+ assert.match(output, /\.claude\/harness-everything\/manifest\.json/);
1032
+ assert.deepStrictEqual(fs.readFileSync(hostFile), before, 'null host docLocation manifest stays byte-identical');
1033
+ });
1034
+
1035
+ for (const [field, value] of [['tracker', 0], ['issueDefinition', false]]) {
1036
+ checkCase(`2aw issue331 p2b. ${leg} host ${field} ${JSON.stringify(value)} fails init without writing the manifest`, () => {
1037
+ const root = workspace(`issue331-p2b-${leg}-${field}-scalar`);
1038
+ const hostConfig = { docLocation: 'docs/', tracker: 'GitHub Issues', issueDefinition: 'Title and acceptance' };
1039
+ hostConfig[field] = value;
1040
+ const hostFile = writeHostConfig(root, 'claude', hostConfig);
1041
+ const before = fs.readFileSync(hostFile);
1042
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
1043
+ const output = `${result.stdout}\n${result.stderr}`;
1044
+ assert.strictEqual(result.status, 1, `${leg} non-string host ${field} must fail init: ${output}`);
1045
+ assert.match(output, /\.claude\/harness-everything\/manifest\.json/);
1046
+ assert.match(output, new RegExp(field));
1047
+ assert.deepStrictEqual(fs.readFileSync(hostFile), before, `${field} scalar host manifest stays byte-identical`);
1048
+ });
1049
+ }
1050
+
1051
+ checkCase(`2aw issue331 p2b. ${leg} host manifest symlink to a regular manifest outside the workspace fails init without changing the target`, () => {
1052
+ const root = workspace(`issue331-p2b-${leg}-outside-file-link`);
1053
+ const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-2aw-p2b-outside-'));
1054
+ try {
1055
+ const target = path.join(outside, 'manifest.json');
1056
+ fs.writeFileSync(target, JSON.stringify({ projectDocs: { docLocation: 'docs/', tracker: 'GitHub Issues' } }, null, 2), 'utf8');
1057
+ const linkFile = path.join(root, '.claude', 'harness-everything', 'manifest.json');
1058
+ fs.mkdirSync(path.dirname(linkFile), { recursive: true });
1059
+ if (!trySymlink(target, linkFile, `${leg} outside file-link case`, 'file')) return;
1060
+ const before = fs.readFileSync(target);
1061
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
1062
+ const output = `${result.stdout}\n${result.stderr}`;
1063
+ assert.strictEqual(result.status, 1, `${leg} host symlink to an outside manifest must fail init: ${output}`);
1064
+ assert.match(output, /\.claude\/harness-everything\/manifest\.json/);
1065
+ assert.deepStrictEqual(fs.readFileSync(target), before, 'outside manifest bytes stay unchanged');
1066
+ } finally {
1067
+ fs.rmSync(outside, { recursive: true, force: true });
1068
+ }
1069
+ });
1070
+
1071
+ checkCase(`2aw issue331 p2c. ${leg} absent host manifest under a parent symlink to outside fails init without creating it`, () => {
1072
+ const root = workspace(`issue331-p2c-${leg}-absent-leaf-outside-parent`);
1073
+ const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-2aw-p2c-outside-'));
1074
+ try {
1075
+ const hostDir = path.join(root, '.claude');
1076
+ fs.mkdirSync(hostDir, { recursive: true });
1077
+ const linkDir = path.join(hostDir, 'harness-everything');
1078
+ if (!trySymlink(outside, linkDir, `${leg} absent-leaf outside-parent case`, 'dir')) return;
1079
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
1080
+ const output = `${result.stdout}\n${result.stderr}`;
1081
+ assert.strictEqual(result.status, 1, `${leg} absent manifest under an escaping parent must fail init: ${output}`);
1082
+ assert.match(output, /nearest existing parent resolves outside the workspace/);
1083
+ assert.match(output, /regular file that resolves inside this workspace/);
1084
+ assert.deepStrictEqual(fs.readdirSync(outside), [], 'no manifest is created outside the workspace');
1085
+ } finally {
1086
+ fs.rmSync(outside, { recursive: true, force: true });
1087
+ }
1088
+ });
1089
+
1090
+ checkCase(`2aw issue331 p2c. ${leg} FIFO host manifest fails init before any read or write`, () => {
1091
+ if (process.platform === 'win32') {
1092
+ console.log(`SKIP ${leg} FIFO case: mkfifo is unavailable on win32`);
1093
+ return;
1094
+ }
1095
+ const root = workspace(`issue331-p2c-${leg}-fifo`);
1096
+ const fifo = path.join(root, '.claude', 'harness-everything', 'manifest.json');
1097
+ fs.mkdirSync(path.dirname(fifo), { recursive: true });
1098
+ execFileSync('mkfifo', [fifo]);
1099
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
1100
+ const output = `${result.stdout}\n${result.stderr}`;
1101
+ assert.strictEqual(result.status, 1, `${leg} FIFO manifest must fail init: ${output}`);
1102
+ assert.match(output, /not a regular file/);
1103
+ assert.ok(fs.lstatSync(fifo).isFIFO(), 'FIFO is left in place');
1104
+ });
1105
+
1106
+ checkCase(`2aw issue331 p2b. ${leg} host manifest symlink to a regular manifest inside the workspace fills the target and keeps the link`, () => {
1107
+ const root = workspace(`issue331-p2b-${leg}-inside-file-link`);
1108
+ const target = path.join(root, 'shared', 'manifest.json');
1109
+ fs.mkdirSync(path.dirname(target), { recursive: true });
1110
+ fs.writeFileSync(target, JSON.stringify({ projectDocs: { governed: [{ path: 'docs/prd/**', role: 'prd' }], domains: ['GRPH'] } }, null, 2), 'utf8');
1111
+ const linkFile = path.join(root, '.claude', 'harness-everything', 'manifest.json');
1112
+ fs.mkdirSync(path.dirname(linkFile), { recursive: true });
1113
+ if (!trySymlink(target, linkFile, `${leg} inside file-link case`, 'file')) return;
1114
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
1115
+ assert.strictEqual(result.status, 0, `${leg}: ${result.stdout}\n${result.stderr}`);
1116
+ assert.ok(fs.lstatSync(linkFile).isSymbolicLink(), 'host manifest path must remain a symlink');
1117
+ const after = JSON.parse(fs.readFileSync(target, 'utf8'));
1118
+ assert.strictEqual(after.projectDocs.docLocation, 'docs/', 'in-workspace target gets docLocation');
1119
+ assert.strictEqual(after.projectDocs.tracker, 'GitHub Issues', 'in-workspace target gets tracker');
1120
+ assert.strictEqual(after.projectDocs.issueDefinition, 'Title and acceptance', 'in-workspace target gets issueDefinition');
1121
+ assert.deepStrictEqual(after.projectDocs.governed, [{ path: 'docs/prd/**', role: 'prd' }], 'governed preserved');
1122
+ });
1123
+
1124
+ checkCase(`2aw issue331 p2b. ${leg} host manifest path that is a directory fails init and leaves a valid peer unwritten`, () => {
1125
+ const root = workspace(`issue331-p2b-${leg}-manifest-directory`);
1126
+ const dirManifest = path.join(root, '.claude', 'harness-everything', 'manifest.json');
1127
+ fs.mkdirSync(dirManifest, { recursive: true });
1128
+ const peerFile = writeHostConfig(root, 'codex', { governed: [{ path: 'docs/prd/**', role: 'prd' }], domains: ['GRPH'] });
1129
+ const peerBefore = fs.readFileSync(peerFile);
1130
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
1131
+ const output = `${result.stdout}\n${result.stderr}`;
1132
+ assert.strictEqual(result.status, 1, `${leg} directory host manifest must fail init: ${output}`);
1133
+ assert.match(output, /\.claude\/harness-everything\/manifest\.json/);
1134
+ assert.deepStrictEqual(fs.readFileSync(peerFile), peerBefore, 'valid peer host manifest stays byte-identical');
1135
+ });
1136
+
1137
+ checkCase(`2aw issue331 p2b. ${leg} host directory symlink to an outside directory fails init and leaves the outside manifest unchanged`, () => {
1138
+ const root = workspace(`issue331-p2b-${leg}-outside-dir-link`);
1139
+ const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-2aw-p2b-outside-'));
1140
+ try {
1141
+ const outsideDir = path.join(outside, 'harness-everything');
1142
+ fs.mkdirSync(outsideDir, { recursive: true });
1143
+ const target = path.join(outsideDir, 'manifest.json');
1144
+ fs.writeFileSync(target, JSON.stringify({ projectDocs: { docLocation: 'docs/', tracker: 'GitHub Issues' } }, null, 2), 'utf8');
1145
+ fs.mkdirSync(path.join(root, '.claude'), { recursive: true });
1146
+ if (!trySymlink(outsideDir, path.join(root, '.claude', 'harness-everything'), `${leg} outside dir-link case`, 'dir')) return;
1147
+ const before = fs.readFileSync(target);
1148
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
1149
+ const output = `${result.stdout}\n${result.stderr}`;
1150
+ assert.strictEqual(result.status, 1, `${leg} host directory symlink to outside must fail init: ${output}`);
1151
+ assert.match(output, /\.claude\/harness-everything\/manifest\.json/);
1152
+ assert.deepStrictEqual(fs.readFileSync(target), before, 'outside manifest bytes stay unchanged');
1153
+ } finally {
1154
+ fs.rmSync(outside, { recursive: true, force: true });
1155
+ }
1156
+ });
1157
+
1158
+ checkCase(`2aw issue331 p2b. ${leg} dangling authoritative project-file symlink to outside fails init without creating the target`, () => {
1159
+ const root = workspace(`issue331-p2b-${leg}-project-dangling`);
1160
+ const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-2aw-p2b-outside-'));
1161
+ try {
1162
+ const target = path.join(outside, 'created-project-docs.json');
1163
+ const projectLink = configPath(root);
1164
+ fs.mkdirSync(path.dirname(projectLink), { recursive: true });
1165
+ if (!trySymlink(target, projectLink, `${leg} dangling project-link case`, 'file')) return;
1166
+ const result = runToSpecInit(root, ['--doc-location', 'docs/', '--tracker', 'GitHub Issues', '--issue-definition', 'Title and acceptance'], scriptPath);
1167
+ const output = `${result.stdout}\n${result.stderr}`;
1168
+ assert.strictEqual(result.status, 1, `${leg} dangling project-file symlink must fail init: ${output}`);
1169
+ assert.ok(!fs.existsSync(target), 'project-file symlink target outside the workspace must not be created');
1170
+ assert.strictEqual(fs.readlinkSync(projectLink), target, 'project-file symlink must still point at its original target');
1171
+ for (const home of Object.values(HOSTS)) {
1172
+ assert.ok(!fs.existsSync(path.join(root, home, 'harness-everything', 'manifest.json')), 'no host manifest may be written when the project file is unsafe');
1173
+ }
1174
+ } finally {
1175
+ fs.rmSync(outside, { recursive: true, force: true });
1176
+ }
1177
+ });
1178
+ }
1179
+
1180
+ for (const [label, resolverFile] of [
1181
+ ['to-spec', toSpecResolverPath],
1182
+ ['multi-agent-workspace', resolverPath],
1183
+ ['harness-everything', harnessResolverPath],
1184
+ ['packaged to-spec', packagedToSpecResolverPath],
1185
+ ['packaged multi-agent-workspace', packagedMultiAgentResolverPath],
1186
+ ['packaged harness-everything', packagedHarnessResolverPath]
1187
+ ]) {
1188
+ checkCase(`2aw issue331 p2b. ${label} resolver rejects non-string docLocation and accepts whitespace-only docLocation`, () => {
1189
+ const { validateDocsConfig } = require(resolverFile);
1190
+ assert.strictEqual(typeof validateDocsConfig, 'function', 'validateDocsConfig export is required');
1191
+ const rejected = validateDocsConfig({ docLocation: 42, domains: ['GRPH'] });
1192
+ assert.strictEqual(rejected.valid, false, `${label}: numeric docLocation must be invalid: ${JSON.stringify(rejected.diagnostics)}`);
1193
+ assert.ok(JSON.stringify(rejected.diagnostics).includes('docLocation'), `diagnostic must name docLocation: ${JSON.stringify(rejected.diagnostics)}`);
1194
+ const accepted = validateDocsConfig({ docLocation: ' ', domains: ['GRPH'] });
1195
+ assert.strictEqual(accepted.valid, true, `${label}: whitespace-only docLocation must stay fillable: ${JSON.stringify(accepted.diagnostics)}`);
1196
+ });
1197
+ }
1198
+
759
1199
  helper.finish();