@justjoshn/pi-pstack 0.15.36-pi.1 → 0.15.36-pi.7

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 (114) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +15 -12
  3. package/agents/ci-watcher.md +27 -0
  4. package/agents/thermo-nuclear-code-quality-review.md +23 -0
  5. package/assets/logo.png +0 -0
  6. package/automations/benny/FOR_AGENTS.md +1 -1
  7. package/automations/benny/README.md +2 -2
  8. package/automations/benny/skills/setup-benny/SKILL.md +5 -5
  9. package/bin/pstack-routines-supervisor +9 -0
  10. package/docs/guide/07-overnight.md +1 -1
  11. package/docs/guide/09-make-it-yours.md +1 -1
  12. package/docs/guide/10-recipes-and-pitfalls.md +1 -1
  13. package/docs/native-gaps.md +11 -5
  14. package/docs/parity-reverse-engineering.md +94 -0
  15. package/package.json +41 -6
  16. package/packaging/install-supervisor-unit.sh +164 -0
  17. package/packaging/launchd/com.pstack.routines-supervisor.plist.example +24 -0
  18. package/packaging/systemd/pstack-routines-supervisor.service.example +15 -0
  19. package/skills/architect/SKILL.md +1 -1
  20. package/skills/architect/references/runner-prompt.md +1 -1
  21. package/skills/arena/SKILL.md +3 -3
  22. package/skills/check-compiler-errors/SKILL.md +23 -0
  23. package/skills/create-skill/SKILL.md +515 -29
  24. package/skills/fix-ci/SKILL.md +29 -0
  25. package/skills/fix-merge-conflicts/SKILL.md +32 -0
  26. package/skills/get-pr-comments/SKILL.md +23 -0
  27. package/skills/how/SKILL.md +1 -1
  28. package/skills/ketchup-video/SKILL.md +2 -2
  29. package/skills/loop-on-ci/SKILL.md +50 -0
  30. package/skills/make-bot-ui/SKILL.md +2 -2
  31. package/skills/make-pr-easy-to-review/SKILL.md +59 -0
  32. package/skills/new-branch-and-pr/SKILL.md +29 -0
  33. package/skills/no-inline-imports/SKILL.md +12 -0
  34. package/skills/poteto-help/SKILL.md +4 -4
  35. package/skills/poteto-mode/playbooks/multi-phase-plan.md +1 -1
  36. package/skills/poteto-mode/playbooks/orchestrate.md +1 -1
  37. package/skills/pr-review-canvas/SKILL.md +176 -0
  38. package/skills/pr-review-canvas/renderer.js +170 -0
  39. package/skills/pr-review-canvas/styles.css +96 -0
  40. package/skills/pr-review-canvas/template.html +18 -0
  41. package/skills/reflect/SKILL.md +1 -1
  42. package/skills/review-and-ship/SKILL.md +41 -0
  43. package/skills/run-smoke-tests/SKILL.md +42 -0
  44. package/skills/statechart/scripts/test/cli.test.ts +1 -1
  45. package/skills/swarm/SKILL.md +2 -2
  46. package/skills/thermo-nuclear-code-quality-review/SKILL.md +192 -0
  47. package/skills/typescript-exhaustive-switch/SKILL.md +10 -0
  48. package/skills/verify-this/SKILL.md +74 -0
  49. package/skills/weekly-review/SKILL.md +32 -0
  50. package/skills/what-did-i-get-done/SKILL.md +31 -0
  51. package/skills/why/SKILL.md +1 -1
  52. package/skills/workflow-from-chats/SKILL.md +50 -0
  53. package/src/mode.ts +3 -3
  54. package/src/paths.ts +13 -0
  55. package/src/pi-command.ts +12 -0
  56. package/src/rule.ts +4 -4
  57. package/src/sections.ts +2 -1
  58. package/src/subscriptions/durable-store.ts +482 -0
  59. package/src/subscriptions/durable-types.ts +41 -0
  60. package/src/subscriptions/ensure-supervisor.ts +67 -0
  61. package/src/subscriptions/index.ts +231 -271
  62. package/src/subscriptions/supervisor-main.ts +88 -0
  63. package/src/subscriptions/supervisor.ts +271 -0
  64. package/src/subscriptions/webhook.ts +22 -9
  65. package/src/task/card.ts +29 -0
  66. package/src/task/records.ts +3 -8
  67. package/src/task/runner.ts +9 -14
  68. package/src/task/tool.ts +18 -21
  69. package/src/todo.ts +1 -2
  70. package/scripts/live-e2e.mjs +0 -257
  71. package/test/aliases.test.ts +0 -57
  72. package/test/ask.test.ts +0 -85
  73. package/test/autocomplete.test.ts +0 -39
  74. package/test/e2e/aliases.e2e.test.ts +0 -81
  75. package/test/e2e/ask.e2e.test.ts +0 -135
  76. package/test/e2e/harness.ts +0 -179
  77. package/test/e2e/host.e2e.test.ts +0 -193
  78. package/test/e2e/lifecycle.e2e.test.ts +0 -99
  79. package/test/e2e/mode.e2e.test.ts +0 -160
  80. package/test/e2e/package.e2e.test.ts +0 -67
  81. package/test/e2e/task.e2e.test.ts +0 -734
  82. package/test/e2e/todo.e2e.test.ts +0 -59
  83. package/test/fixtures/cursor-askquestion-calls.json +0 -806
  84. package/test/fixtures/fake-pi.mjs +0 -96
  85. package/test/fixtures/image-model.ts +0 -16
  86. package/test/fixtures/list-models.ts +0 -15
  87. package/test/fixtures/readonly-shell-exit.ts +0 -17
  88. package/test/fixtures/scripted-model.ts +0 -113
  89. package/test/loop.test.ts +0 -209
  90. package/test/mode.test.ts +0 -143
  91. package/test/plan.test.ts +0 -126
  92. package/test/readonly-shell-security.test.ts +0 -84
  93. package/test/readonly-shell.test.ts +0 -44
  94. package/test/records.test.ts +0 -143
  95. package/test/registry.test.ts +0 -18
  96. package/test/resources.test.ts +0 -162
  97. package/test/rule.test.ts +0 -92
  98. package/test/scripts.test.ts +0 -182
  99. package/test/sections.test.ts +0 -56
  100. package/test/subscriptions-cron.test.ts +0 -60
  101. package/test/subscriptions.test.ts +0 -598
  102. package/test/task-agents.test.ts +0 -49
  103. package/test/task-card.test.ts +0 -34
  104. package/test/task-fork.test.ts +0 -233
  105. package/test/task-format.test.ts +0 -65
  106. package/test/task-model.test.ts +0 -141
  107. package/test/task-runner.test.ts +0 -259
  108. package/test/task-schema.test.ts +0 -50
  109. package/test/task-status.test.ts +0 -155
  110. package/test/task-worktree.test.ts +0 -106
  111. package/test/todo.test.ts +0 -74
  112. package/test/visualize.test.ts +0 -152
  113. package/tsconfig.json +0 -5
  114. package/vitest.config.ts +0 -16
@@ -1,152 +0,0 @@
1
- import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync } from 'node:fs';
2
- import { tmpdir } from 'node:os';
3
- import { join } from 'node:path';
4
-
5
- import type { ExtensionAPI, ExtensionToolContext, ToolDefinition } from '@earendil-works/pi-coding-agent';
6
- import { expect, onTestFinished, test } from 'vitest';
7
- import { type Chart, chartError, chartSlug, MAX_POINTS, registerVisualize, renderChartSvg, renderChartText } from '../src/visualize.ts';
8
-
9
- const bar: Chart = { title: 'Latency by route', kind: 'bar', labels: ['/a', '/bb', '/c'], series: [{ name: 'p50', values: [10, 40, 0] }], unit: 'ms' };
10
- const line: Chart = {
11
- title: 'Load',
12
- kind: 'line',
13
- labels: ['mon', 'tue', 'wed', 'thu'],
14
- series: [
15
- { name: 'cpu', values: [1, 5, 3, 9] },
16
- { name: 'mem', values: [2, 2, 8, 4] },
17
- ],
18
- };
19
-
20
- /** A minimal well-formedness check: every tag closes in order and every attribute is quoted. */
21
- function assertWellFormed(xml: string): void {
22
- const body = xml.replace(/^<\?xml[^>]*\?>\s*/, '');
23
- const stack: string[] = [];
24
- for (const match of body.matchAll(/<(\/?)([A-Za-z][\w:-]*)((?:\s+[\w:-]+="[^"<]*")*)\s*(\/?)>/g)) {
25
- const [, closing, name, , selfClosing] = match;
26
- if (selfClosing) continue;
27
- if (closing) expect(stack.pop()).toBe(name);
28
- else stack.push(name as string);
29
- }
30
- expect(stack).toEqual([]);
31
- // Every `<` starts a tag the pattern accepted, so no tag was skipped as malformed.
32
- const tags = body.match(/<[^>]*>/g) ?? [];
33
- expect(tags.length).toBe([...body.matchAll(/<(\/?)([A-Za-z][\w:-]*)((?:\s+[\w:-]+="[^"<]*")*)\s*(\/?)>/g)].length);
34
- expect(body.replace(/<[^>]*>/g, '')).not.toMatch(/[<>]|&(?!(lt|gt|amp|apos|quot);)/);
35
- }
36
-
37
- test('bars scale to the largest value and show each value', () => {
38
- const lines = renderChartText(bar).split('\n');
39
- expect(lines[0]).toBe('Latency by route');
40
- const rows = lines.slice(2);
41
- expect(rows).toHaveLength(3);
42
- const width = (row: string) => (row.match(/█/g) ?? []).length;
43
- expect(width(rows[1] as string)).toBe(40);
44
- expect(width(rows[0] as string)).toBe(10);
45
- expect(width(rows[2] as string)).toBe(0);
46
- expect(rows[1]).toMatch(/^\/bb │█+ 40 ms$/);
47
- expect((rows[0] as string).indexOf('│')).toBe((rows[1] as string).indexOf('│'));
48
- });
49
-
50
- test('several bar series get distinct fills and a legend', () => {
51
- const text = renderChartText({ ...bar, series: [...bar.series, { name: 'p99', values: [20, 80, 1] }] });
52
- expect(text).toContain('▓');
53
- expect(text.split('\n').at(-1)).toBe('█ p50 ▓ p99');
54
- expect(text.split('\n').filter((row) => row.includes('│'))).toHaveLength(6);
55
- });
56
-
57
- test('the line plot marks each series and labels the axis range', () => {
58
- const lines = renderChartText(line).split('\n');
59
- expect(lines[2]).toMatch(/^9 ┤/);
60
- expect(lines[11]).toMatch(/^1 ┤/);
61
- expect(lines[12]).toMatch(/└─{4}$/);
62
- expect(lines[13]).toContain('mon');
63
- expect(lines[13]).toContain('thu');
64
- expect(lines.at(-1)).toBe('● cpu ○ mem');
65
- const plot = lines.slice(2, 12).join('\n');
66
- expect((plot.match(/●/g) ?? []).length).toBe(4);
67
- expect(plot.match(/○/g)?.length).toBeGreaterThanOrEqual(3);
68
- });
69
-
70
- test('a flat or single-point line still draws', () => {
71
- expect(renderChartText({ title: 't', kind: 'line', labels: ['x'], series: [{ name: 's', values: [3] }] })).toContain('●');
72
- expect(renderChartText({ title: 't', kind: 'line', labels: ['a', 'b'], series: [{ name: 's', values: [2, 2] }] })).toContain('●');
73
- });
74
-
75
- test('the SVG is well formed with title, axes, legend, and escaped text', () => {
76
- for (const chart of [bar, line, { ...line, title: 'a < b & "c"', series: [{ name: '<x>', values: [-1, 2, 3, 4] }] }]) {
77
- const svg = renderChartSvg(chart);
78
- assertWellFormed(svg);
79
- expect(svg).toContain('xmlns="http://www.w3.org/2000/svg"');
80
- expect(svg).not.toMatch(/NaN|Infinity/);
81
- }
82
- expect(renderChartSvg(bar)).toContain('>Latency by route</text>');
83
- expect(renderChartSvg(bar).match(/<rect [^>]*fill="#4e79a7"/g)).toHaveLength(4);
84
- expect(renderChartSvg(line)).toContain('<polyline');
85
- expect(renderChartSvg(line)).toContain('>mem</text>');
86
- expect(renderChartSvg({ ...line, title: 'a < b & "c"' })).toContain('a &lt; b &amp; &quot;c&quot;');
87
- });
88
-
89
- test('rejects shapes it cannot draw, with the reason', () => {
90
- expect(chartError(bar)).toBeUndefined();
91
- expect(chartError({ ...bar, labels: [], series: [{ name: 's', values: [] }] })).toContain('labels is empty');
92
- expect(chartError({ ...bar, series: [] })).toContain('series is empty');
93
- expect(chartError({ ...bar, series: [{ name: 's', values: [1, 2] }] })).toContain('has 2 values for 3 labels');
94
- expect(chartError({ ...bar, series: [{ name: 's', values: [1, Number.NaN, 2] }] })).toContain('value 1 (label "/bb") is NaN');
95
- expect(chartError({ ...bar, series: [{ name: 's', values: [1, 2, Number.POSITIVE_INFINITY] }] })).toContain('not a finite number');
96
- const many = Array.from({ length: MAX_POINTS + 1 }, (_, index) => String(index));
97
- expect(chartError({ ...bar, labels: many, series: [{ name: 's', values: many.map(Number) }] })).toContain('too many points');
98
- });
99
-
100
- test('slugs titles for file names', () => {
101
- expect(chartSlug('Latency by route (p50)!')).toBe('latency-by-route-p50');
102
- expect(chartSlug('***')).toBe('chart');
103
- });
104
-
105
- function tool(dir: string): ToolDefinition {
106
- const tools: ToolDefinition[] = [];
107
- registerVisualize({ registerTool: (definition: ToolDefinition) => tools.push(definition) } as unknown as ExtensionAPI, { artifactsDir: () => join(dir, 'artifacts'), now: () => new Date('2026-10-28T16:44:01.813Z') });
108
- return tools[0] as ToolDefinition;
109
- }
110
-
111
- test('the tool returns the chart and writes the SVG to the artifacts folder', async () => {
112
- const dir = mkdtempSync(join(tmpdir(), 'pi-pstack-viz-'));
113
- onTestFinished(() => rmSync(dir, { recursive: true, force: true }));
114
- const result = await tool(dir).execute('call', bar, undefined, undefined, {} as ExtensionToolContext);
115
- const path = join(dir, 'artifacts', '20261028T164401Z-latency-by-route.svg');
116
- expect((result.details as { path: string }).path).toBe(path);
117
- const text = result.content[0]?.type === 'text' ? result.content[0].text : '';
118
- expect(text).toContain('Latency by route');
119
- expect(text).toContain(`SVG: ${path}`);
120
- assertWellFormed(readFileSync(path, 'utf8'));
121
- });
122
-
123
- test('the tool fails on a bad shape and writes nothing', async () => {
124
- const dir = mkdtempSync(join(tmpdir(), 'pi-pstack-viz-'));
125
- onTestFinished(() => rmSync(dir, { recursive: true, force: true }));
126
- await expect(tool(dir).execute('call', { ...bar, series: [{ name: 's', values: [1] }] }, undefined, undefined, {} as ExtensionToolContext)).rejects.toThrow('has 1 values for 3 labels');
127
- expect(existsSync(join(dir, 'artifacts')) ? readdirSync(join(dir, 'artifacts')) : []).toEqual([]);
128
- });
129
-
130
- test('rejects more series than a chart can draw', () => {
131
- const series = Array.from({ length: 33 }, (_, index) => ({ name: `s${index}`, values: [1, 2, 3] }));
132
- expect(chartError({ ...bar, series })).toContain('too many series');
133
- });
134
-
135
- test('a long series does not crash range math', () => {
136
- const values = Array.from({ length: 150_000 }, () => 1);
137
- const labels = values.map((_, index) => String(index));
138
- expect(renderChartText({ title: 'wide', kind: 'line', labels, series: [{ name: 's', values }] })).toContain('wide');
139
- });
140
-
141
- test('two charts with the same title in the same second do not overwrite', async () => {
142
- const dir = mkdtempSync(join(tmpdir(), 'pi-pstack-viz-'));
143
- onTestFinished(() => rmSync(dir, { recursive: true, force: true }));
144
- const visualize = tool(dir);
145
- const first = await visualize.execute('a', bar, undefined, undefined, {} as ExtensionToolContext);
146
- const second = await visualize.execute('b', { ...bar, series: [{ name: 'p50', values: [1, 2, 3] }] }, undefined, undefined, {} as ExtensionToolContext);
147
- const path1 = (first.details as { path: string }).path;
148
- const path2 = (second.details as { path: string }).path;
149
- expect(path2).not.toBe(path1);
150
- expect(readFileSync(path1, 'utf8')).toContain('p50 /bb: 40 ms');
151
- expect(readdirSync(join(dir, 'artifacts'))).toHaveLength(2);
152
- });
package/tsconfig.json DELETED
@@ -1,5 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "include": ["src/**/*.ts", "test/**/*.ts"],
4
- "exclude": ["node_modules", "coverage", "skills"]
5
- }
package/vitest.config.ts DELETED
@@ -1,16 +0,0 @@
1
- import { configDefaults, defineConfig } from 'vitest/config';
2
-
3
- export default defineConfig({
4
- test: {
5
- environment: 'node',
6
- globals: false,
7
- pool: 'forks',
8
- isolate: true,
9
- testTimeout: 60_000,
10
- exclude: [...configDefaults.exclude, 'skills/**', 'automations/**', 'docs/**'],
11
- coverage: {
12
- provider: 'v8',
13
- include: ['src/**/*.ts'],
14
- },
15
- },
16
- });