pi-harness-runtime 1.1.404 → 1.1.406

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 (158) hide show
  1. package/index.ts +275 -1
  2. package/package.json +1 -1
  3. package/packages/a2a-adapter/package.json +1 -1
  4. package/packages/agent-policy/package.json +1 -1
  5. package/packages/architecture-generator/package.json +1 -1
  6. package/packages/auth/package.json +1 -1
  7. package/packages/autonomous-refactor/package.json +1 -1
  8. package/packages/autonomous-runtime/package.json +1 -1
  9. package/packages/cache-strategy/package.json +1 -1
  10. package/packages/capability-registry/package.json +1 -1
  11. package/packages/checkpoint/package.json +1 -1
  12. package/packages/cli-plugin-sdk/package.json +1 -1
  13. package/packages/clipboard/package.json +1 -1
  14. package/packages/clipboard-plugin/package.json +1 -1
  15. package/packages/code-generation/package.json +1 -1
  16. package/packages/code-review/package.json +1 -1
  17. package/packages/codex-adapter/package.json +1 -1
  18. package/packages/codex-watcher/package.json +1 -1
  19. package/packages/config-capture/package.json +1 -1
  20. package/packages/connection-runtime/package.json +1 -1
  21. package/packages/context-compiler/package.json +1 -1
  22. package/packages/context-discovery/package.json +1 -1
  23. package/packages/context-manager/package.json +1 -1
  24. package/packages/contracts/package.json +1 -1
  25. package/packages/cookie-sanitizer/package.json +1 -1
  26. package/packages/cost-optimizer/package.json +1 -1
  27. package/packages/dependency-analyzer/package.json +1 -1
  28. package/packages/django-plugin/package.json +1 -1
  29. package/packages/doc-generator/package.json +1 -1
  30. package/packages/durable-review/package.json +1 -1
  31. package/packages/e2e-tester/package.json +1 -1
  32. package/packages/evaluation-engine/package.json +1 -1
  33. package/packages/evaluation-runner/package.json +1 -1
  34. package/packages/event-bus/package.json +1 -1
  35. package/packages/event-sink/package.json +1 -1
  36. package/packages/event-store/package.json +1 -1
  37. package/packages/experience-replay/package.json +1 -1
  38. package/packages/feedback-collector/package.json +1 -1
  39. package/packages/file-copy-helper/package.json +1 -1
  40. package/packages/framework-detector/package.json +1 -1
  41. package/packages/framework-plugin-sdk/package.json +1 -1
  42. package/packages/frappe-plugin/package.json +1 -1
  43. package/packages/generic-web-plugin/package.json +1 -1
  44. package/packages/health-monitor/package.json +1 -1
  45. package/packages/intent-analyzer/package.json +1 -1
  46. package/packages/jev-judge/package.json +1 -1
  47. package/packages/knowledge-graph/package.json +1 -1
  48. package/packages/knowledge-retrieval/package.json +1 -1
  49. package/packages/laravel-plugin/package.json +1 -1
  50. package/packages/learning-engine/package.json +1 -1
  51. package/packages/logging/package.json +1 -1
  52. package/packages/mcp-adapter/package.json +1 -1
  53. package/packages/memory-engine/package.json +1 -1
  54. package/packages/milestone-manager/package.json +1 -1
  55. package/packages/model-registry/package.json +1 -1
  56. package/packages/nextjs-plugin/package.json +1 -1
  57. package/packages/notification/dist/adapters/line-adapter.d.ts.map +1 -1
  58. package/packages/notification/dist/adapters/line-adapter.js +6 -1
  59. package/packages/notification/dist/adapters/line-adapter.js.map +1 -1
  60. package/packages/notification/dist/notification-center.d.ts.map +1 -1
  61. package/packages/notification/dist/notification-center.js +32 -0
  62. package/packages/notification/dist/notification-center.js.map +1 -1
  63. package/packages/notification/dist/telegram-webhook-handler.d.ts +7 -0
  64. package/packages/notification/dist/telegram-webhook-handler.d.ts.map +1 -1
  65. package/packages/notification/dist/telegram-webhook-handler.js +7 -0
  66. package/packages/notification/dist/telegram-webhook-handler.js.map +1 -1
  67. package/packages/notification/dist/types.d.ts +1 -1
  68. package/packages/notification/dist/types.d.ts.map +1 -1
  69. package/packages/notification/package.json +1 -1
  70. package/packages/notification/src/adapters/line-adapter.ts +7 -1
  71. package/packages/notification/src/notification-center.ts +30 -0
  72. package/packages/notification/src/telegram-webhook-handler.ts +7 -0
  73. package/packages/notification/src/types.ts +1 -0
  74. package/packages/notification/test/notification.test.ts +31 -28
  75. package/packages/notification/test/tooling-proposal-payload.test.ts +212 -0
  76. package/packages/observability/package.json +1 -1
  77. package/packages/okf-indexer/package.json +1 -1
  78. package/packages/paperclip-integration/package.json +1 -1
  79. package/packages/performance-optimizer/package.json +1 -1
  80. package/packages/permission-mode/package.json +1 -1
  81. package/packages/privilege-broker/package.json +1 -1
  82. package/packages/project-analyzer/package.json +1 -1
  83. package/packages/project-bootstrap/package.json +1 -1
  84. package/packages/projection-engine/package.json +1 -1
  85. package/packages/prompt-compiler/package.json +1 -1
  86. package/packages/prompt-versioning/package.json +1 -1
  87. package/packages/provider-adapter-sdk/package.json +1 -1
  88. package/packages/provider-router/package.json +1 -1
  89. package/packages/provider-selector/package.json +1 -1
  90. package/packages/providers/package.json +1 -1
  91. package/packages/qdrant-skills/package.json +1 -1
  92. package/packages/quota-manager/package.json +1 -1
  93. package/packages/rate-limiter/package.json +1 -1
  94. package/packages/react-vite-plugin/package.json +1 -1
  95. package/packages/relay/package.json +1 -1
  96. package/packages/release-manager/package.json +1 -1
  97. package/packages/requirement-compiler/package.json +1 -1
  98. package/packages/runtime/package.json +1 -1
  99. package/packages/scheduler/package.json +1 -1
  100. package/packages/scheduler-adapter/package.json +1 -1
  101. package/packages/session/package.json +1 -1
  102. package/packages/session-api/package.json +1 -1
  103. package/packages/session-export/package.json +1 -1
  104. package/packages/shared-context/package.json +1 -1
  105. package/packages/skill-mcp-client/package.json +1 -1
  106. package/packages/skill-registry/package.json +1 -1
  107. package/packages/skills/package.json +1 -1
  108. package/packages/sprint-planner/package.json +1 -1
  109. package/packages/subscription-engine/package.json +1 -1
  110. package/packages/task-compiler/package.json +1 -1
  111. package/packages/telegram/package.json +1 -1
  112. package/packages/tencentdb-memory/package.json +1 -1
  113. package/packages/tencentdb-sync/package.json +1 -1
  114. package/packages/test-data-generator/package.json +1 -1
  115. package/packages/test-generator/package.json +1 -1
  116. package/packages/todo-bd-sync/package.json +1 -1
  117. package/packages/token-estimation/package.json +1 -1
  118. package/packages/token-optimizer/package.json +1 -1
  119. package/packages/tooling-preference/dist/detect.d.ts +62 -0
  120. package/packages/tooling-preference/dist/detect.d.ts.map +1 -0
  121. package/packages/tooling-preference/dist/detect.js +209 -0
  122. package/packages/tooling-preference/dist/detect.js.map +1 -0
  123. package/packages/tooling-preference/dist/index.d.ts +13 -0
  124. package/packages/tooling-preference/dist/index.d.ts.map +1 -0
  125. package/packages/tooling-preference/dist/index.js +13 -0
  126. package/packages/tooling-preference/dist/index.js.map +1 -0
  127. package/packages/tooling-preference/dist/proposal.d.ts +70 -0
  128. package/packages/tooling-preference/dist/proposal.d.ts.map +1 -0
  129. package/packages/tooling-preference/dist/proposal.js +137 -0
  130. package/packages/tooling-preference/dist/proposal.js.map +1 -0
  131. package/packages/tooling-preference/dist/registry.d.ts +50 -0
  132. package/packages/tooling-preference/dist/registry.d.ts.map +1 -0
  133. package/packages/tooling-preference/dist/registry.js +53 -0
  134. package/packages/tooling-preference/dist/registry.js.map +1 -0
  135. package/packages/tooling-preference/dist/store.d.ts +61 -0
  136. package/packages/tooling-preference/dist/store.d.ts.map +1 -0
  137. package/packages/tooling-preference/dist/store.js +129 -0
  138. package/packages/tooling-preference/dist/store.js.map +1 -0
  139. package/packages/tooling-preference/package.json +21 -0
  140. package/packages/tooling-preference/src/detect.ts +251 -0
  141. package/packages/tooling-preference/src/index.ts +44 -0
  142. package/packages/tooling-preference/src/proposal.ts +173 -0
  143. package/packages/tooling-preference/src/registry.ts +79 -0
  144. package/packages/tooling-preference/src/store.ts +162 -0
  145. package/packages/tooling-preference/tests/detect.test.ts +188 -0
  146. package/packages/tooling-preference/tests/proposal.test.ts +107 -0
  147. package/packages/tooling-preference/tests/registry.test.ts +51 -0
  148. package/packages/tooling-preference/tests/store.test.ts +99 -0
  149. package/packages/trajectory/package.json +1 -1
  150. package/packages/tui/package.json +1 -1
  151. package/packages/types/package.json +1 -1
  152. package/packages/voice-api/package.json +1 -1
  153. package/packages/webmcp-tester/package.json +1 -1
  154. package/packages/wiki/package.json +1 -1
  155. package/packages/workflow-events/package.json +1 -1
  156. package/packages/workspace-scanner/package.json +1 -1
  157. package/packages/worktree/package.json +1 -1
  158. package/packages/write-review/package.json +1 -1
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The pending proposals of this session.
3
+ *
4
+ * The first answer wins: a second click on the other button changes
5
+ * nothing. The registry is in memory only - a proposal that was never
6
+ * answered simply disappears with the session.
7
+ */
8
+ export class ToolingRequestRegistry {
9
+ pending = new Map();
10
+ resolved = new Map();
11
+ /**
12
+ * Register a proposal. A request ID that is already known is refused.
13
+ */
14
+ register(request) {
15
+ if (this.pending.has(request.requestId) || this.resolved.has(request.requestId)) {
16
+ return false;
17
+ }
18
+ this.pending.set(request.requestId, request);
19
+ return true;
20
+ }
21
+ /**
22
+ * Is the request still waiting for an answer?
23
+ */
24
+ has(requestId) {
25
+ return this.pending.has(requestId);
26
+ }
27
+ /**
28
+ * Claim a request for an answer.
29
+ *
30
+ * The claim removes the request, so a second click finds nothing and the
31
+ * first answer stays the one that counts.
32
+ */
33
+ claim(requestId, choice, channel) {
34
+ const request = this.pending.get(requestId);
35
+ if (!request)
36
+ return null;
37
+ this.pending.delete(requestId);
38
+ const resolution = {
39
+ choice,
40
+ preference: choice === "yes" ? "prefer_biome" : "use_project_tools",
41
+ ...(channel ? { channel } : {}),
42
+ };
43
+ this.resolved.set(requestId, resolution);
44
+ return { request, resolution };
45
+ }
46
+ /**
47
+ * The answer of a request that was already resolved.
48
+ */
49
+ resolutionOf(requestId) {
50
+ return this.resolved.get(requestId);
51
+ }
52
+ }
53
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAqBH,MAAM,OAAO,sBAAsB;IACjB,OAAO,GAAG,IAAI,GAAG,EAA0B,CAAC;IAC5C,QAAQ,GAAG,IAAI,GAAG,EAA6B,CAAC;IAEjE;;OAEG;IACH,QAAQ,CAAC,OAAuB;QAC/B,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;YACjF,OAAO,KAAK,CAAC;QACd,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAC7C,OAAO,IAAI,CAAC;IACb,CAAC;IAED;;OAEG;IACH,GAAG,CAAC,SAAiB;QACpB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACpC,CAAC;IAED;;;;;OAKG;IACH,KAAK,CACJ,SAAiB,EACjB,MAAqB,EACrB,OAAgB;QAEhB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC1B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC/B,MAAM,UAAU,GAAsB;YACrC,MAAM;YACN,UAAU,EAAE,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,mBAAmB;YACnE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC/B,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;QACzC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;IAChC,CAAC;IAED;;OAEG;IACH,YAAY,CAAC,SAAiB;QAC7B,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACrC,CAAC;CACD"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The saved tooling preference of a user in a workspace.
3
+ *
4
+ * The store is a plain JSON file under the harness runtime directory: it
5
+ * works in every runtime (a SQLite file would not - `node:sqlite` is
6
+ * missing in some of them) and it stays readable for the user.
7
+ */
8
+ import type { ToolingPreference } from "./proposal.js";
9
+ export interface ToolingPreferenceRecord {
10
+ /** The saved decision, when the user answered. */
11
+ decision?: ToolingPreference;
12
+ /** When the proposal was sent. */
13
+ askedAt?: string;
14
+ /** When the user answered. */
15
+ decidedAt?: string;
16
+ /** The request ID of the proposal that was sent. */
17
+ requestId?: string;
18
+ /** The workspace the record belongs to. */
19
+ workspaceRoot?: string;
20
+ /** The channel the answer came from. */
21
+ channel?: string;
22
+ }
23
+ /**
24
+ * The key of one record: the user and the workspace.
25
+ */
26
+ export declare function toolingPreferenceKey(userKey: string, workspaceRoot: string): string;
27
+ /**
28
+ * The runtime directory of the harness.
29
+ */
30
+ export declare function harnessRuntimeDir(): string;
31
+ export declare class ToolingPreferenceStore {
32
+ private readonly filePath;
33
+ constructor(filePath: string);
34
+ /**
35
+ * The default store: `~/.pi-harness-runtime/tooling-preference.json`.
36
+ */
37
+ static defaultFilePath(): string;
38
+ /**
39
+ * The record of one user in one workspace.
40
+ */
41
+ read(userKey: string, workspaceRoot: string): ToolingPreferenceRecord | undefined;
42
+ /**
43
+ * Remember that the proposal was sent.
44
+ */
45
+ markAsked(userKey: string, workspaceRoot: string, requestId: string, now?: Date): void;
46
+ /**
47
+ * Save the answer of the user.
48
+ */
49
+ recordDecision(userKey: string, workspaceRoot: string, decision: ToolingPreference, requestId: string, channel?: string, now?: Date): void;
50
+ private load;
51
+ private save;
52
+ }
53
+ /**
54
+ * May the proposal be sent again?
55
+ *
56
+ * A workspace that was asked and never answered is asked again after the
57
+ * cooldown - the user may have missed it. A workspace with a decision is
58
+ * never asked again.
59
+ */
60
+ export declare function shouldAskAgain(record: ToolingPreferenceRecord | undefined, cooldownMs: number, now?: Date): boolean;
61
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD,MAAM,WAAW,uBAAuB;IACvC,kDAAkD;IAClD,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,kCAAkC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8BAA8B;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAQD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAEnF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAI1C;AAED,qBAAa,sBAAsB;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAArC,YAA6B,QAAQ,EAAE,MAAM,EAAI;IAEjD;;OAEG;IACH,MAAM,CAAC,eAAe,IAAI,MAAM,CAE/B;IAED;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,uBAAuB,GAAG,SAAS,CAGhF;IAED;;OAEG;IACH,SAAS,CACR,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,GAAG,GAAE,IAAiB,GACpB,IAAI,CAWN;IAED;;OAEG;IACH,cAAc,CACb,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,MAAM,EAChB,GAAG,GAAE,IAAiB,GACpB,IAAI,CAcN;IAED,OAAO,CAAC,IAAI;IAaZ,OAAO,CAAC,IAAI;CAYZ;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC7B,MAAM,EAAE,uBAAuB,GAAG,SAAS,EAC3C,UAAU,EAAE,MAAM,EAClB,GAAG,GAAE,IAAiB,GACpB,OAAO,CAOT"}
@@ -0,0 +1,129 @@
1
+ /**
2
+ * The saved tooling preference of a user in a workspace.
3
+ *
4
+ * The store is a plain JSON file under the harness runtime directory: it
5
+ * works in every runtime (a SQLite file would not - `node:sqlite` is
6
+ * missing in some of them) and it stays readable for the user.
7
+ */
8
+ import { createHash } from "node:crypto";
9
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { dirname, join } from "node:path";
12
+ function shortHash(value, length = 16) {
13
+ return createHash("sha256").update(value).digest("hex").slice(0, length);
14
+ }
15
+ /**
16
+ * The key of one record: the user and the workspace.
17
+ */
18
+ export function toolingPreferenceKey(userKey, workspaceRoot) {
19
+ return `${userKey}::${shortHash(workspaceRoot)}`;
20
+ }
21
+ /**
22
+ * The runtime directory of the harness.
23
+ */
24
+ export function harnessRuntimeDir() {
25
+ const configured = process.env.PI_HARNESS_HOME?.trim();
26
+ if (configured)
27
+ return configured;
28
+ return join(homedir(), ".pi-harness-runtime");
29
+ }
30
+ export class ToolingPreferenceStore {
31
+ filePath;
32
+ constructor(filePath) {
33
+ this.filePath = filePath;
34
+ }
35
+ /**
36
+ * The default store: `~/.pi-harness-runtime/tooling-preference.json`.
37
+ */
38
+ static defaultFilePath() {
39
+ return join(harnessRuntimeDir(), "tooling-preference.json");
40
+ }
41
+ /**
42
+ * The record of one user in one workspace.
43
+ */
44
+ read(userKey, workspaceRoot) {
45
+ const data = this.load();
46
+ return data[toolingPreferenceKey(userKey, workspaceRoot)];
47
+ }
48
+ /**
49
+ * Remember that the proposal was sent.
50
+ */
51
+ markAsked(userKey, workspaceRoot, requestId, now = new Date()) {
52
+ const key = toolingPreferenceKey(userKey, workspaceRoot);
53
+ const data = this.load();
54
+ const existing = data[key] ?? {};
55
+ data[key] = {
56
+ ...existing,
57
+ askedAt: now.toISOString(),
58
+ requestId,
59
+ workspaceRoot,
60
+ };
61
+ this.save(data);
62
+ }
63
+ /**
64
+ * Save the answer of the user.
65
+ */
66
+ recordDecision(userKey, workspaceRoot, decision, requestId, channel, now = new Date()) {
67
+ const key = toolingPreferenceKey(userKey, workspaceRoot);
68
+ const data = this.load();
69
+ const existing = data[key] ?? {};
70
+ data[key] = {
71
+ ...existing,
72
+ decision,
73
+ decidedAt: now.toISOString(),
74
+ askedAt: existing.askedAt ?? now.toISOString(),
75
+ requestId,
76
+ workspaceRoot,
77
+ ...(channel ? { channel } : {}),
78
+ };
79
+ this.save(data);
80
+ }
81
+ load() {
82
+ if (!existsSync(this.filePath))
83
+ return {};
84
+ try {
85
+ const parsed = JSON.parse(readFileSync(this.filePath, "utf8"));
86
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
87
+ return {};
88
+ return parsed;
89
+ }
90
+ catch {
91
+ // A broken store must not stop the harness: the proposal is
92
+ // simply asked again.
93
+ return {};
94
+ }
95
+ }
96
+ save(data) {
97
+ try {
98
+ mkdirSync(dirname(this.filePath), { recursive: true });
99
+ const temporary = `${this.filePath}.tmp`;
100
+ writeFileSync(temporary, `${JSON.stringify(data, null, 2)}\n`, "utf8");
101
+ // The rename keeps a half written file from being read.
102
+ renameSync(temporary, this.filePath);
103
+ }
104
+ catch {
105
+ // A store that cannot be written is a lost preference, not a
106
+ // broken harness.
107
+ }
108
+ }
109
+ }
110
+ /**
111
+ * May the proposal be sent again?
112
+ *
113
+ * A workspace that was asked and never answered is asked again after the
114
+ * cooldown - the user may have missed it. A workspace with a decision is
115
+ * never asked again.
116
+ */
117
+ export function shouldAskAgain(record, cooldownMs, now = new Date()) {
118
+ if (!record)
119
+ return true;
120
+ if (record.decision)
121
+ return false;
122
+ if (!record.askedAt)
123
+ return true;
124
+ const askedAt = Date.parse(record.askedAt);
125
+ if (Number.isNaN(askedAt))
126
+ return true;
127
+ return now.getTime() - askedAt >= cooldownMs;
128
+ }
129
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@pi-harness/tooling-preference",
3
+ "version": "1.1.406",
4
+ "description": "pi-harness-runtime — JS/TS tooling preference (Biome vs ESLint/Prettier) proposal",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "test": "bun test"
20
+ }
21
+ }
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Tooling detection for a workspace.
3
+ *
4
+ * The proposal ("use Biome.js instead of ESLint + Prettier") is only made
5
+ * for a JavaScript/TypeScript workspace that still uses ESLint or Prettier
6
+ * and does not use Biome yet. The detection reads the workspace itself -
7
+ * it never guesses from the harness.
8
+ */
9
+
10
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
11
+ import { join } from "node:path";
12
+
13
+ export interface ToolingContext {
14
+ /** Absolute workspace root. */
15
+ root: string;
16
+ /** The workspace has a package.json. */
17
+ hasPackageJson: boolean;
18
+ /** The workspace is a JavaScript/TypeScript project. */
19
+ isJavaScriptTypeScript: boolean;
20
+ /** Next.js is in use. */
21
+ isNextJs: boolean;
22
+ /** React is in use. */
23
+ isReact: boolean;
24
+ /** Biome is configured or installed. */
25
+ usesBiome: boolean;
26
+ /** ESLint is configured or installed. */
27
+ usesEslint: boolean;
28
+ /** Prettier is configured or installed. */
29
+ usesPrettier: boolean;
30
+ /** The package manager the workspace uses, if it can be told. */
31
+ packageManager: string | null;
32
+ /** The frameworks that were detected. */
33
+ frameworks: string[];
34
+ }
35
+
36
+ const ESLINT_CONFIGS = [
37
+ ".eslintrc",
38
+ ".eslintrc.js",
39
+ ".eslintrc.cjs",
40
+ ".eslintrc.mjs",
41
+ ".eslintrc.json",
42
+ ".eslintrc.yaml",
43
+ ".eslintrc.yml",
44
+ "eslint.config.js",
45
+ "eslint.config.mjs",
46
+ "eslint.config.cjs",
47
+ "eslint.config.ts",
48
+ ];
49
+
50
+ const PRETTIER_CONFIGS = [
51
+ ".prettierrc",
52
+ ".prettierrc.json",
53
+ ".prettierrc.yaml",
54
+ ".prettierrc.yml",
55
+ ".prettierrc.js",
56
+ ".prettierrc.cjs",
57
+ ".prettierrc.mjs",
58
+ "prettier.config.js",
59
+ "prettier.config.cjs",
60
+ "prettier.config.mjs",
61
+ "prettier.config.ts",
62
+ ];
63
+
64
+ const BIOME_CONFIGS = ["biome.json", "biome.jsonc", "biome.json5"];
65
+
66
+ const NEXT_CONFIGS = [
67
+ "next.config.js",
68
+ "next.config.mjs",
69
+ "next.config.cjs",
70
+ "next.config.ts",
71
+ ];
72
+
73
+ const SOURCE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
74
+
75
+ const IGNORED_DIRECTORIES = new Set([
76
+ "node_modules",
77
+ "dist",
78
+ "build",
79
+ "out",
80
+ "coverage",
81
+ "vendor",
82
+ ".git",
83
+ ".next",
84
+ ".turbo",
85
+ ".cache",
86
+ ]);
87
+
88
+ interface PackageJson {
89
+ name?: string;
90
+ dependencies?: Record<string, string>;
91
+ devDependencies?: Record<string, string>;
92
+ peerDependencies?: Record<string, string>;
93
+ packageManager?: string;
94
+ }
95
+
96
+ function readPackageJson(root: string): PackageJson | null {
97
+ const path = join(root, "package.json");
98
+ if (!existsSync(path)) return null;
99
+ try {
100
+ const parsed = JSON.parse(readFileSync(path, "utf8")) as unknown;
101
+ if (!parsed || typeof parsed !== "object") return null;
102
+ return parsed as PackageJson;
103
+ } catch {
104
+ return null;
105
+ }
106
+ }
107
+
108
+ function allDependencies(pkg: PackageJson | null): Record<string, string> {
109
+ if (!pkg) return {};
110
+ return {
111
+ ...(pkg.dependencies ?? {}),
112
+ ...(pkg.devDependencies ?? {}),
113
+ ...(pkg.peerDependencies ?? {}),
114
+ };
115
+ }
116
+
117
+ function anyExists(root: string, names: string[]): boolean {
118
+ return names.some((name) => existsSync(join(root, name)));
119
+ }
120
+
121
+ /**
122
+ * Is there a JavaScript/TypeScript source file in the workspace?
123
+ *
124
+ * The scan is shallow on purpose: it only answers "does this workspace hold
125
+ * JS/TS code at all", so it stops at the first source file and never walks
126
+ * a build or dependency directory.
127
+ */
128
+ function hasSourceFile(root: string, maxDepth = 2): boolean {
129
+ const queue: Array<{ dir: string; depth: number }> = [{ dir: root, depth: 0 }];
130
+ while (queue.length > 0) {
131
+ const current = queue.shift();
132
+ if (!current) break;
133
+ let entries: string[];
134
+ try {
135
+ entries = readdirSync(current.dir);
136
+ } catch {
137
+ continue;
138
+ }
139
+ for (const entry of entries) {
140
+ if (entry.startsWith(".")) continue;
141
+ const full = join(current.dir, entry);
142
+ let isDirectory = false;
143
+ try {
144
+ isDirectory = statSync(full).isDirectory();
145
+ } catch {
146
+ continue;
147
+ }
148
+ if (isDirectory) {
149
+ if (current.depth < maxDepth && !IGNORED_DIRECTORIES.has(entry)) {
150
+ queue.push({ dir: full, depth: current.depth + 1 });
151
+ }
152
+ continue;
153
+ }
154
+ if (SOURCE_EXTENSIONS.some((extension) => entry.endsWith(extension))) {
155
+ return true;
156
+ }
157
+ }
158
+ }
159
+ return false;
160
+ }
161
+
162
+ /**
163
+ * Which package manager does the workspace use?
164
+ *
165
+ * The `packageManager` field wins; a lockfile decides otherwise.
166
+ */
167
+ export function detectPackageManager(root: string, pkg: PackageJson | null): string | null {
168
+ const declared = pkg?.packageManager?.split("@")[0]?.trim();
169
+ if (declared) return declared;
170
+ if (existsSync(join(root, "pnpm-lock.yaml"))) return "pnpm";
171
+ if (existsSync(join(root, "yarn.lock"))) return "yarn";
172
+ if (existsSync(join(root, "bun.lock")) || existsSync(join(root, "bun.lockb"))) return "bun";
173
+ if (existsSync(join(root, "package-lock.json"))) return "npm";
174
+ return null;
175
+ }
176
+
177
+ /**
178
+ * Read the tooling facts of a workspace.
179
+ */
180
+ export function detectToolingContext(root: string): ToolingContext {
181
+ const pkg = readPackageJson(root);
182
+ const deps = allDependencies(pkg);
183
+ const hasPackageJson = pkg !== null;
184
+ const hasTsConfig = existsSync(join(root, "tsconfig.json"));
185
+ const isNextJs = Boolean(deps.next) || anyExists(root, NEXT_CONFIGS);
186
+ const isReact = Boolean(deps.react || deps["react-dom"]) || isNextJs;
187
+ const isTypeScript =
188
+ hasTsConfig || Boolean(deps.typescript) || hasSourceFile(root);
189
+
190
+ return {
191
+ root,
192
+ hasPackageJson,
193
+ isJavaScriptTypeScript: hasPackageJson && isTypeScript,
194
+ isNextJs,
195
+ isReact,
196
+ usesBiome: anyExists(root, BIOME_CONFIGS) || Boolean(deps["@biomejs/biome"]),
197
+ usesEslint: anyExists(root, ESLINT_CONFIGS) || Boolean(deps.eslint),
198
+ usesPrettier: anyExists(root, PRETTIER_CONFIGS) || Boolean(deps.prettier),
199
+ packageManager: detectPackageManager(root, pkg),
200
+ frameworks: [
201
+ ...(isNextJs ? ["next"] : []),
202
+ ...(isReact && !isNextJs ? ["react"] : []),
203
+ ],
204
+ };
205
+ }
206
+
207
+ export interface ProposalEligibility {
208
+ /** May the proposal be sent? */
209
+ eligible: boolean;
210
+ /** Why the proposal is (not) sent - a human wording. */
211
+ reason: string;
212
+ }
213
+
214
+ /**
215
+ * May the Biome proposal be sent for this workspace?
216
+ *
217
+ * A workspace that already uses Biome is never asked about Biome, and a
218
+ * workspace without ESLint and Prettier has nothing to replace.
219
+ */
220
+ export function evaluateBiomeProposalEligibility(
221
+ context: ToolingContext,
222
+ ): ProposalEligibility {
223
+ if (!context.hasPackageJson) {
224
+ return {
225
+ eligible: false,
226
+ reason: "the workspace has no package.json",
227
+ };
228
+ }
229
+ if (!context.isJavaScriptTypeScript) {
230
+ return {
231
+ eligible: false,
232
+ reason: "the workspace is not a JavaScript/TypeScript project",
233
+ };
234
+ }
235
+ if (context.usesBiome) {
236
+ return {
237
+ eligible: false,
238
+ reason: "Biome is already configured in this workspace",
239
+ };
240
+ }
241
+ if (!context.usesEslint && !context.usesPrettier) {
242
+ return {
243
+ eligible: false,
244
+ reason: "the workspace uses neither ESLint nor Prettier",
245
+ };
246
+ }
247
+ return {
248
+ eligible: true,
249
+ reason: "the workspace uses ESLint and/or Prettier and no Biome",
250
+ };
251
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The JS/TS tooling preference of a workspace.
3
+ *
4
+ * The harness offers the user to replace ESLint and Prettier with Biome.js
5
+ * in a JavaScript/TypeScript workspace. The offer is a decision of its own:
6
+ * it never pauses the work and never travels through the task-approval
7
+ * path.
8
+ */
9
+
10
+ export {
11
+ detectPackageManager,
12
+ detectToolingContext,
13
+ evaluateBiomeProposalEligibility,
14
+ type ProposalEligibility,
15
+ type ToolingContext,
16
+ } from "./detect.js";
17
+
18
+ export {
19
+ buildToolingButtons,
20
+ buildToolingImplementationPrompt,
21
+ buildToolingProposalMessage,
22
+ buildToolingRequestId,
23
+ buildToolingUserKey,
24
+ parseToolingTarget,
25
+ TOOLING_ACTION,
26
+ type ToolingButton,
27
+ type ToolingChoice,
28
+ type ToolingPreference,
29
+ type ToolingTarget,
30
+ } from "./proposal.js";
31
+
32
+ export {
33
+ harnessRuntimeDir,
34
+ shouldAskAgain,
35
+ ToolingPreferenceStore,
36
+ toolingPreferenceKey,
37
+ type ToolingPreferenceRecord,
38
+ } from "./store.js";
39
+
40
+ export {
41
+ ToolingRequestRegistry,
42
+ type ToolingRequest,
43
+ type ToolingResolution,
44
+ } from "./registry.js";