@nonameolsson/nightjar 0.0.0-stage → 0.1.0

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 (173) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +341 -2
  3. package/dist/adapter-module.d.ts +18 -0
  4. package/dist/adapter-module.js +83 -0
  5. package/dist/adapter-module.js.map +1 -0
  6. package/dist/adapters.d.ts +15 -0
  7. package/dist/adapters.js +160 -0
  8. package/dist/adapters.js.map +1 -0
  9. package/dist/args.d.ts +12 -0
  10. package/dist/args.js +79 -0
  11. package/dist/args.js.map +1 -0
  12. package/dist/ask.d.ts +6 -0
  13. package/dist/ask.js +27 -0
  14. package/dist/ask.js.map +1 -0
  15. package/dist/azure-devops/api.d.ts +34 -0
  16. package/dist/azure-devops/api.js +98 -0
  17. package/dist/azure-devops/api.js.map +1 -0
  18. package/dist/azure-devops/code-host.d.ts +36 -0
  19. package/dist/azure-devops/code-host.js +185 -0
  20. package/dist/azure-devops/code-host.js.map +1 -0
  21. package/dist/azure-devops/fake-azure-devops.d.ts +90 -0
  22. package/dist/azure-devops/fake-azure-devops.js +728 -0
  23. package/dist/azure-devops/fake-azure-devops.js.map +1 -0
  24. package/dist/azure-devops/index.d.ts +8 -0
  25. package/dist/azure-devops/index.js +11 -0
  26. package/dist/azure-devops/index.js.map +1 -0
  27. package/dist/azure-devops/sign-in.d.ts +11 -0
  28. package/dist/azure-devops/sign-in.js +68 -0
  29. package/dist/azure-devops/sign-in.js.map +1 -0
  30. package/dist/azure-devops/text.d.ts +1 -0
  31. package/dist/azure-devops/text.js +41 -0
  32. package/dist/azure-devops/text.js.map +1 -0
  33. package/dist/azure-devops/tracker.d.ts +44 -0
  34. package/dist/azure-devops/tracker.js +333 -0
  35. package/dist/azure-devops/tracker.js.map +1 -0
  36. package/dist/build.d.ts +72 -0
  37. package/dist/build.js +442 -0
  38. package/dist/build.js.map +1 -0
  39. package/dist/check.d.ts +8 -0
  40. package/dist/check.js +25 -0
  41. package/dist/check.js.map +1 -0
  42. package/dist/checks.d.ts +38 -0
  43. package/dist/checks.js +179 -0
  44. package/dist/checks.js.map +1 -0
  45. package/dist/cli.d.ts +2 -0
  46. package/dist/cli.js +11 -0
  47. package/dist/cli.js.map +1 -0
  48. package/dist/code-host.d.ts +19 -0
  49. package/dist/code-host.js +2 -0
  50. package/dist/code-host.js.map +1 -0
  51. package/dist/config.d.ts +65 -0
  52. package/dist/config.js +215 -0
  53. package/dist/config.js.map +1 -0
  54. package/dist/contract/case.d.ts +4 -0
  55. package/dist/contract/case.js +2 -0
  56. package/dist/contract/case.js.map +1 -0
  57. package/dist/contract/code-host.d.ts +24 -0
  58. package/dist/contract/code-host.js +108 -0
  59. package/dist/contract/code-host.js.map +1 -0
  60. package/dist/contract/command.d.ts +2 -0
  61. package/dist/contract/command.js +36 -0
  62. package/dist/contract/command.js.map +1 -0
  63. package/dist/contract/feature-writer.d.ts +3 -0
  64. package/dist/contract/feature-writer.js +101 -0
  65. package/dist/contract/feature-writer.js.map +1 -0
  66. package/dist/contract/run.d.ts +18 -0
  67. package/dist/contract/run.js +89 -0
  68. package/dist/contract/run.js.map +1 -0
  69. package/dist/contract/tracker.d.ts +30 -0
  70. package/dist/contract/tracker.js +250 -0
  71. package/dist/contract/tracker.js.map +1 -0
  72. package/dist/create.d.ts +9 -0
  73. package/dist/create.js +112 -0
  74. package/dist/create.js.map +1 -0
  75. package/dist/doctor.d.ts +4 -0
  76. package/dist/doctor.js +37 -0
  77. package/dist/doctor.js.map +1 -0
  78. package/dist/fakes/code-host.d.ts +21 -0
  79. package/dist/fakes/code-host.js +42 -0
  80. package/dist/fakes/code-host.js.map +1 -0
  81. package/dist/fakes/harness.d.ts +7 -0
  82. package/dist/fakes/harness.js +68 -0
  83. package/dist/fakes/harness.js.map +1 -0
  84. package/dist/fakes/machine.d.ts +10 -0
  85. package/dist/fakes/machine.js +94 -0
  86. package/dist/fakes/machine.js.map +1 -0
  87. package/dist/fakes/tracker.d.ts +40 -0
  88. package/dist/fakes/tracker.js +116 -0
  89. package/dist/fakes/tracker.js.map +1 -0
  90. package/dist/feature-file.d.ts +12 -0
  91. package/dist/feature-file.js +84 -0
  92. package/dist/feature-file.js.map +1 -0
  93. package/dist/git.d.ts +9 -0
  94. package/dist/git.js +12 -0
  95. package/dist/git.js.map +1 -0
  96. package/dist/github/api.d.ts +37 -0
  97. package/dist/github/api.js +66 -0
  98. package/dist/github/api.js.map +1 -0
  99. package/dist/github/code-host.d.ts +19 -0
  100. package/dist/github/code-host.js +105 -0
  101. package/dist/github/code-host.js.map +1 -0
  102. package/dist/github/fake-gh.d.ts +17 -0
  103. package/dist/github/fake-gh.js +51 -0
  104. package/dist/github/fake-gh.js.map +1 -0
  105. package/dist/github/fake-github.d.ts +58 -0
  106. package/dist/github/fake-github.js +285 -0
  107. package/dist/github/fake-github.js.map +1 -0
  108. package/dist/github/labels.d.ts +3 -0
  109. package/dist/github/labels.js +44 -0
  110. package/dist/github/labels.js.map +1 -0
  111. package/dist/github/tracker.d.ts +28 -0
  112. package/dist/github/tracker.js +208 -0
  113. package/dist/github/tracker.js.map +1 -0
  114. package/dist/index.d.ts +16 -0
  115. package/dist/index.js +13 -0
  116. package/dist/index.js.map +1 -0
  117. package/dist/init.d.ts +18 -0
  118. package/dist/init.js +217 -0
  119. package/dist/init.js.map +1 -0
  120. package/dist/ledger.d.ts +13 -0
  121. package/dist/ledger.js +49 -0
  122. package/dist/ledger.js.map +1 -0
  123. package/dist/main.d.ts +11 -0
  124. package/dist/main.js +199 -0
  125. package/dist/main.js.map +1 -0
  126. package/dist/person-config.d.ts +8 -0
  127. package/dist/person-config.js +41 -0
  128. package/dist/person-config.js.map +1 -0
  129. package/dist/plan.d.ts +9 -0
  130. package/dist/plan.js +27 -0
  131. package/dist/plan.js.map +1 -0
  132. package/dist/ports.d.ts +16 -0
  133. package/dist/ports.js +104 -0
  134. package/dist/ports.js.map +1 -0
  135. package/dist/prompt.d.ts +103 -0
  136. package/dist/prompt.js +143 -0
  137. package/dist/prompt.js.map +1 -0
  138. package/dist/queue.d.ts +20 -0
  139. package/dist/queue.js +94 -0
  140. package/dist/queue.js.map +1 -0
  141. package/dist/say.d.ts +2 -0
  142. package/dist/say.js +4 -0
  143. package/dist/say.js.map +1 -0
  144. package/dist/session.d.ts +39 -0
  145. package/dist/session.js +144 -0
  146. package/dist/session.js.map +1 -0
  147. package/dist/settings.d.ts +4 -0
  148. package/dist/settings.js +83 -0
  149. package/dist/settings.js.map +1 -0
  150. package/dist/signals.d.ts +5 -0
  151. package/dist/signals.js +13 -0
  152. package/dist/signals.js.map +1 -0
  153. package/dist/status-shape.d.ts +53 -0
  154. package/dist/status-shape.js +3 -0
  155. package/dist/status-shape.js.map +1 -0
  156. package/dist/status.d.ts +23 -0
  157. package/dist/status.js +56 -0
  158. package/dist/status.js.map +1 -0
  159. package/dist/tracker.d.ts +56 -0
  160. package/dist/tracker.js +12 -0
  161. package/dist/tracker.js.map +1 -0
  162. package/dist/worktree.d.ts +28 -0
  163. package/dist/worktree.js +144 -0
  164. package/dist/worktree.js.map +1 -0
  165. package/docs/ticket-contract.md +85 -0
  166. package/package.json +74 -5
  167. package/plugin/nightjar-status/.claude-plugin/plugin.json +5 -0
  168. package/plugin/nightjar-status/hooks/format.ts +48 -0
  169. package/plugin/nightjar-status/hooks/hooks.json +1 -0
  170. package/plugin/nightjar-status/hooks/register.ts +51 -0
  171. package/schema/factory.schema.json +330 -0
  172. package/skills/nightjar-feature/SKILL.md +73 -0
  173. package/src/status-shape.ts +44 -0
@@ -0,0 +1,108 @@
1
+ // The Code host contract (docs/adr/0001): what every Code host Adapter must do, fake, built in or a repo's own.
2
+ import assert from 'node:assert/strict';
3
+ function codeHostCase(name, check) {
4
+ return {
5
+ name,
6
+ run: async (harness) => {
7
+ const subject = await harness();
8
+ try {
9
+ await check(subject);
10
+ }
11
+ finally {
12
+ await subject.close?.();
13
+ }
14
+ }
15
+ };
16
+ }
17
+ export const codeHostContract = [
18
+ codeHostCase("pushes a worktree's HEAD to a branch, and moves it on with a later push", async ({ codeHost, commit, branch }) => {
19
+ const first = await commit();
20
+ await codeHost.push(first.folder, 'factory/1-a');
21
+ assert.equal(await branch('factory/1-a'), first.commit);
22
+ assert.equal(await branch('factory/2-b'), null);
23
+ const second = await commit();
24
+ await codeHost.push(second.folder, 'factory/1-a');
25
+ assert.equal(await branch('factory/1-a'), second.commit);
26
+ }),
27
+ codeHostCase('opens a draft pull request with the title, branch, base and body it is given', async ({ codeHost, commit, base, pull }) => {
28
+ await codeHost.push((await commit()).folder, 'factory/1-a');
29
+ const body = 'Nightjar built all of it.\n\n## Built\n\n- `$(x)` "quoted" & <b>more</b>';
30
+ const opened = await codeHost.createDraft({
31
+ title: 'One $(escape)',
32
+ head: 'factory/1-a',
33
+ base,
34
+ body
35
+ });
36
+ assert.ok(opened.url !== '', 'the pull request has a URL');
37
+ const shown = await pull(opened);
38
+ assert.deepEqual({ title: shown.title, head: shown.head, base: shown.base, draft: shown.draft }, { title: 'One $(escape)', head: 'factory/1-a', base, draft: true });
39
+ assert.ok(shown.body.includes(body), `the body holds what it was given:\n${shown.body}`);
40
+ assert.deepEqual(shown.references, []);
41
+ assert.deepEqual(shown.comments, []);
42
+ }),
43
+ codeHostCase('finds the open pull request of a branch, and none for a branch without one', async ({ codeHost, commit, base }) => {
44
+ const { folder } = await commit();
45
+ assert.equal(await codeHost.openPull('factory/1-a'), null);
46
+ await codeHost.push(folder, 'factory/1-a');
47
+ await codeHost.push(folder, 'factory/2-b');
48
+ await codeHost.push(folder, 'factory/3-c');
49
+ const one = await codeHost.createDraft({ title: 'A', head: 'factory/1-a', base, body: 'a' });
50
+ const two = await codeHost.createDraft({ title: 'B', head: 'factory/2-b', base, body: 'b' });
51
+ assert.notEqual(one.id, two.id);
52
+ assert.deepEqual(await codeHost.openPull('factory/1-a'), one);
53
+ assert.deepEqual(await codeHost.openPull('factory/2-b'), two);
54
+ assert.equal(await codeHost.openPull('factory/3-c'), null);
55
+ }),
56
+ codeHostCase('replaces the body of a pull request', async ({ codeHost, commit, base, pull }) => {
57
+ await codeHost.push((await commit()).folder, 'factory/1-a');
58
+ const opened = await codeHost.createDraft({
59
+ title: 'A',
60
+ head: 'factory/1-a',
61
+ base,
62
+ body: 'First body'
63
+ });
64
+ await codeHost.editBody(opened, 'Second body, with `$(x)` & "quotes"');
65
+ const { body } = await pull(opened);
66
+ assert.ok(body.includes('Second body, with `$(x)` & "quotes"'), `the new body:\n${body}`);
67
+ assert.ok(!body.includes('First body'), `the old body is gone:\n${body}`);
68
+ }),
69
+ codeHostCase('places each Ticket reference once, and keeps them all when the body is replaced', async ({ codeHost, commit, base, pull }) => {
70
+ await codeHost.push((await commit()).folder, 'factory/1-a');
71
+ const opened = await codeHost.createDraft({
72
+ title: 'A',
73
+ head: 'factory/1-a',
74
+ base,
75
+ body: 'First body'
76
+ });
77
+ const sorted = async () => [...(await pull(opened)).references].sort();
78
+ await codeHost.placeReferences(opened, [
79
+ { id: '2', text: '#2' },
80
+ { id: '3', text: '#3' }
81
+ ]);
82
+ assert.deepEqual(await sorted(), ['#2', '#3']);
83
+ await codeHost.placeReferences(opened, [
84
+ { id: '1', text: '#1' },
85
+ { id: '3', text: '#3' }
86
+ ]);
87
+ assert.deepEqual(await sorted(), ['#1', '#2', '#3']);
88
+ await codeHost.editBody(opened, 'Second body');
89
+ assert.deepEqual(await sorted(), ['#1', '#2', '#3']);
90
+ await codeHost.placeReferences(opened, []);
91
+ assert.deepEqual(await sorted(), ['#1', '#2', '#3']);
92
+ assert.ok((await pull(opened)).body.includes('Second body'));
93
+ }),
94
+ codeHostCase('comments on a pull request, oldest first, with the body unchanged', async ({ codeHost, commit, base, pull }) => {
95
+ await codeHost.push((await commit()).folder, 'factory/1-a');
96
+ const opened = await codeHost.createDraft({
97
+ title: 'A',
98
+ head: 'factory/1-a',
99
+ base,
100
+ body: 'a'
101
+ });
102
+ const review = '**Nightjar review: changes**\n\n- `src/a.ts:3`: $(x) & "y"';
103
+ await codeHost.comment(opened, review);
104
+ await codeHost.comment(opened, 'Second');
105
+ assert.deepEqual((await pull(opened)).comments, [review, 'Second']);
106
+ })
107
+ ];
108
+ //# sourceMappingURL=code-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-host.js","sourceRoot":"","sources":["../../src/contract/code-host.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const CONTRACT_USAGE = "usage: nightjar contract MODULE\n\n MODULE a module, as a path from here, that exports trackerHarness and/or codeHostHarness;\n runs the Tracker and Code host contract suites over them and names each behaviour that fails";
2
+ export declare function runContractCommand(argv: readonly string[]): Promise<number>;
@@ -0,0 +1,36 @@
1
+ // `nightjar contract MODULE`: checks a module's Adapters against the contract suites.
2
+ import { importModule } from '../adapter-module.js';
3
+ import { ContractRefusal, formatReport, runContract } from './run.js';
4
+ import { why } from '../say.js';
5
+ export const CONTRACT_USAGE = `usage: nightjar contract MODULE
6
+
7
+ MODULE a module, as a path from here, that exports trackerHarness and/or codeHostHarness;
8
+ runs the Tracker and Code host contract suites over them and names each behaviour that fails`;
9
+ // 0 when every behaviour holds, 1 when one fails, 2 when the module could not be checked.
10
+ export async function runContractCommand(argv) {
11
+ const [path, ...rest] = argv;
12
+ if (path === undefined || rest.length > 0) {
13
+ console.error(`nightjar: ${path === undefined ? 'say which module' : 'one module at a time'}\n\n${CONTRACT_USAGE}`);
14
+ return 2;
15
+ }
16
+ let exports;
17
+ try {
18
+ exports = await importModule(path, process.cwd());
19
+ }
20
+ catch (fault) {
21
+ console.error(`nightjar: contract: cannot load ${path}: ${why(fault)}`);
22
+ return 2;
23
+ }
24
+ try {
25
+ const report = await runContract(exports);
26
+ console.log(formatReport(report));
27
+ return report.failed === 0 ? 0 : 1;
28
+ }
29
+ catch (fault) {
30
+ if (!(fault instanceof ContractRefusal))
31
+ throw fault;
32
+ console.error(`nightjar: contract: ${path} ${fault.message}`);
33
+ return 2;
34
+ }
35
+ }
36
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/contract/command.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACtE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEhC,MAAM,CAAC,MAAM,cAAc,GAAG;;;uGAGyE,CAAC;AAExG,0FAA0F;AAC1F,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAAuB;IAC/D,MAAM,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAC7B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3C,OAAO,CAAC,KAAK,CACZ,aAAa,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,sBAAsB,OAAO,cAAc,EAAE,CACpG,CAAC;QACF,OAAO,CAAC,CAAC;IACV,CAAC;IACD,IAAI,OAAgC,CAAC;IACrC,IAAI,CAAC;QACJ,OAAO,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,mCAAmC,IAAI,KAAK,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACxE,OAAO,CAAC,CAAC;IACV,CAAC;IACD,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;QAClC,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAI,CAAC,CAAC,KAAK,YAAY,eAAe,CAAC;YAAE,MAAM,KAAK,CAAC;QACrD,OAAO,CAAC,KAAK,CAAC,uBAAuB,IAAI,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9D,OAAO,CAAC,CAAC;IACV,CAAC;AACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ContractCase } from './case.js';
2
+ import type { TrackerHarness } from './tracker.js';
3
+ export declare const featureWriterContract: readonly ContractCase<TrackerHarness>[];
@@ -0,0 +1,101 @@
1
+ // The creation contract (docs/ticket-contract.md): what it makes is read back through the Tracker's own methods.
2
+ import assert from 'node:assert/strict';
3
+ import { canCreate } from '../tracker.js';
4
+ const ANN = 'ann';
5
+ function writerCase(name, items, check) {
6
+ return {
7
+ name,
8
+ run: async (harness) => {
9
+ const subject = await harness({ person: ANN, trusted: [ANN], items });
10
+ try {
11
+ const { tracker } = subject;
12
+ assert.ok(canCreate(tracker), 'the Tracker has createFeature, createTicket and block');
13
+ await check({ ...subject, tracker });
14
+ }
15
+ finally {
16
+ await subject.close?.();
17
+ }
18
+ }
19
+ };
20
+ }
21
+ // A Ticket in the contract's shape, with what a person might type in it.
22
+ const TICKET_BODY = [
23
+ '**Model:** opus',
24
+ '**Effort:** high',
25
+ '',
26
+ 'Share the escape with `escapeHtml(text)` & keep <b>bold</b> as text.',
27
+ '',
28
+ '## Acceptance criteria',
29
+ '',
30
+ '- [ ] one escape for both',
31
+ '- [ ] a "quoted" value stays quoted'
32
+ ].join('\n');
33
+ export const featureWriterContract = [
34
+ writerCase('makes a Feature with its title and body, and each Ticket its last child, in the order made', [
35
+ { key: 'other', title: 'Another Feature', children: ['old'] },
36
+ { key: 'old', title: 'An older Ticket' }
37
+ ], async ({ tracker, id }) => {
38
+ const feature = await tracker.createFeature({
39
+ title: 'Share the escape',
40
+ body: 'The spec.\n\n## Solution\n\nOne escape.'
41
+ });
42
+ const made = [];
43
+ for (const title of ['First', 'Second', 'Third']) {
44
+ made.push(await tracker.createTicket(feature, { title, body: `Build ${title}.` }));
45
+ }
46
+ assert.equal(new Set([feature, ...made, id('other'), id('old')]).size, 6, 'each id is new');
47
+ assert.deepEqual(await tracker.feature(feature), {
48
+ id: feature,
49
+ title: 'Share the escape',
50
+ body: 'The spec.\n\n## Solution\n\nOne escape.'
51
+ });
52
+ assert.deepEqual(await tracker.tickets(feature), ['First', 'Second', 'Third'].map((title, i) => ({
53
+ id: made[i],
54
+ title,
55
+ body: `Build ${title}.`,
56
+ open: true,
57
+ signals: [],
58
+ blockedBy: []
59
+ })));
60
+ assert.deepEqual((await tracker.tickets(id('other'))).map((t) => t.id), [id('old')]);
61
+ }),
62
+ writerCase("keeps a Ticket's body as written, so its Model and Effort lines and acceptance criteria read back", [], async ({ tracker }) => {
63
+ const feature = await tracker.createFeature({ title: 'Feature', body: 'The spec.' });
64
+ await tracker.createTicket(feature, { title: 'Ticket', body: TICKET_BODY });
65
+ assert.deepEqual((await tracker.tickets(feature)).map((t) => t.body), [TICKET_BODY]);
66
+ }),
67
+ writerCase('links a Ticket as blocked by another it made, or by an item already there', [
68
+ { key: 'other', title: 'Another Feature', children: ['done'] },
69
+ { key: 'done', title: 'A closed Ticket', open: false }
70
+ ], async ({ tracker, id }) => {
71
+ const feature = await tracker.createFeature({ title: 'Feature', body: '' });
72
+ const a = await tracker.createTicket(feature, { title: 'a', body: 'Build a.' });
73
+ const b = await tracker.createTicket(feature, { title: 'b', body: 'Build b.' });
74
+ const c = await tracker.createTicket(feature, { title: 'c', body: 'Build c.' });
75
+ await tracker.block(b, a);
76
+ await tracker.block(c, a);
77
+ await tracker.block(c, id('done'));
78
+ const byId = (x, y) => x.id.localeCompare(y.id);
79
+ assert.deepEqual((await tracker.tickets(feature)).map((t) => ({
80
+ id: t.id,
81
+ blockedBy: [...t.blockedBy].sort(byId)
82
+ })), [
83
+ { id: a, blockedBy: [] },
84
+ { id: b, blockedBy: [{ id: a, open: true }] },
85
+ {
86
+ id: c,
87
+ blockedBy: [
88
+ { id: a, open: true },
89
+ { id: id('done'), open: false }
90
+ ].sort(byId)
91
+ }
92
+ ]);
93
+ }),
94
+ writerCase('takes a Signal on a Ticket it made', [], async ({ tracker }) => {
95
+ const feature = await tracker.createFeature({ title: 'Feature', body: 'The spec.' });
96
+ const ticket = await tracker.createTicket(feature, { title: 'Ticket', body: 'Build it.' });
97
+ await tracker.addSignal(ticket, 'ready');
98
+ assert.deepEqual((await tracker.tickets(feature)).map((t) => t.signals), [['ready']]);
99
+ })
100
+ ];
101
+ //# sourceMappingURL=feature-writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-writer.js","sourceRoot":"","sources":["../../src/contract/feature-writer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ export declare class ContractRefusal extends Error {
2
+ }
3
+ export interface CaseResult {
4
+ readonly name: string;
5
+ readonly ok: boolean;
6
+ readonly error?: string;
7
+ }
8
+ export interface SuiteResult {
9
+ readonly title: string;
10
+ readonly results: readonly CaseResult[];
11
+ }
12
+ export interface Report {
13
+ readonly suites: readonly SuiteResult[];
14
+ readonly total: number;
15
+ readonly failed: number;
16
+ }
17
+ export declare function runContract(exports: Record<string, unknown>): Promise<Report>;
18
+ export declare function formatReport(report: Report): string;
@@ -0,0 +1,89 @@
1
+ import { codeHostContract } from './code-host.js';
2
+ import { canCreate } from '../tracker.js';
3
+ import { featureWriterContract } from './feature-writer.js';
4
+ import { trackerContract } from './tracker.js';
5
+ import { why } from '../say.js';
6
+ // A module exports the harnesses of the Adapters it wants checked.
7
+ export class ContractRefusal extends Error {
8
+ }
9
+ async function runSuite(title, cases, harness) {
10
+ const results = [];
11
+ for (const c of cases) {
12
+ try {
13
+ await c.run(harness);
14
+ results.push({ name: c.name, ok: true });
15
+ }
16
+ catch (fault) {
17
+ results.push({
18
+ name: c.name,
19
+ ok: false,
20
+ error: why(fault)
21
+ });
22
+ }
23
+ }
24
+ return { title, results };
25
+ }
26
+ function harnessOf(exports, name) {
27
+ const found = exports[name];
28
+ if (found !== undefined && typeof found !== 'function') {
29
+ throw new ContractRefusal(`exports ${name}, which is not a function`);
30
+ }
31
+ return found;
32
+ }
33
+ // A harness that cannot build a Tracker over an empty world has failed every case already, so it is asked nothing more.
34
+ async function makesFeatures(harness) {
35
+ let subject;
36
+ try {
37
+ subject = await harness({ person: 'ann', trusted: ['ann'], items: [] });
38
+ }
39
+ catch {
40
+ return false;
41
+ }
42
+ try {
43
+ return canCreate(subject.tracker);
44
+ }
45
+ finally {
46
+ await subject.close?.();
47
+ }
48
+ }
49
+ // The creation suite runs too when the Tracker makes Features.
50
+ export async function runContract(exports) {
51
+ const tracker = harnessOf(exports, 'trackerHarness');
52
+ const codeHost = harnessOf(exports, 'codeHostHarness');
53
+ if (tracker === undefined && codeHost === undefined) {
54
+ throw new ContractRefusal('exports neither trackerHarness nor codeHostHarness');
55
+ }
56
+ const suites = [];
57
+ if (tracker !== undefined) {
58
+ suites.push(await runSuite('Tracker', trackerContract, tracker));
59
+ if (await makesFeatures(tracker)) {
60
+ suites.push(await runSuite('Tracker creation', featureWriterContract, tracker));
61
+ }
62
+ }
63
+ if (codeHost !== undefined) {
64
+ suites.push(await runSuite('Code host', codeHostContract, codeHost));
65
+ }
66
+ const all = suites.flatMap((s) => s.results);
67
+ return { suites, total: all.length, failed: all.filter((r) => !r.ok).length };
68
+ }
69
+ const indent = (text, by) => text
70
+ .split('\n')
71
+ .map((line) => `${by}${line}`.trimEnd())
72
+ .join('\n');
73
+ export function formatReport(report) {
74
+ const lines = [];
75
+ for (const suite of report.suites) {
76
+ lines.push(`${suite.title} contract`);
77
+ for (const r of suite.results) {
78
+ lines.push(` ${r.ok ? 'ok ' : 'FAIL'} ${r.name}`);
79
+ if (r.error !== undefined)
80
+ lines.push(indent(r.error.trim(), ' '));
81
+ }
82
+ lines.push('');
83
+ }
84
+ lines.push(report.failed === 0
85
+ ? `All ${String(report.total)} behaviours pass.`
86
+ : `${String(report.failed)} of ${String(report.total)} behaviours fail.`);
87
+ return lines.join('\n');
88
+ }
89
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/contract/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { type Signal } from '../signals.js';
2
+ import type { ItemId, Tracker } from '../tracker.js';
3
+ import type { ContractCase } from './case.js';
4
+ export interface WorldComment {
5
+ readonly author: string;
6
+ readonly body: string;
7
+ }
8
+ export interface WorldItem {
9
+ readonly key: string;
10
+ readonly title: string;
11
+ readonly body?: string;
12
+ readonly open?: boolean;
13
+ readonly assignees?: readonly string[];
14
+ readonly signals?: readonly Signal[];
15
+ readonly children?: readonly string[];
16
+ readonly blockedBy?: readonly string[];
17
+ readonly comments?: readonly WorldComment[];
18
+ }
19
+ export interface TrackerWorld {
20
+ readonly person: string;
21
+ readonly trusted: readonly string[];
22
+ readonly items: readonly WorldItem[];
23
+ }
24
+ export interface TrackerSubject {
25
+ readonly tracker: Tracker;
26
+ readonly id: (key: string) => ItemId;
27
+ readonly close?: () => Promise<void>;
28
+ }
29
+ export type TrackerHarness = (world: TrackerWorld) => Promise<TrackerSubject>;
30
+ export declare const trackerContract: readonly ContractCase<TrackerHarness>[];