@phyzess/pappra 0.0.0-stage → 0.1.0-beta.10

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 (227) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +99 -2
  3. package/THIRD_PARTY_NOTICES.md +16 -0
  4. package/assets/integrations/skills/pappra/SKILL.md +71 -0
  5. package/assets/integrations/skills/pappra/references/hosts.md +40 -0
  6. package/assets/integrations/skills/pappra/references/lifecycle.md +41 -0
  7. package/assets/integrations/skills/pappra/references/operations.md +27 -0
  8. package/assets/manifest.json +4 -0
  9. package/dist/app/collaboration.d.ts +170 -0
  10. package/dist/app/collaboration.js +1643 -0
  11. package/dist/app/doctor.d.ts +9 -0
  12. package/dist/app/doctor.js +1030 -0
  13. package/dist/app/init.d.ts +171 -0
  14. package/dist/app/init.js +1442 -0
  15. package/dist/cli/commands/code.d.ts +16 -0
  16. package/dist/cli/commands/code.js +127 -0
  17. package/dist/cli/commands/collaboration.d.ts +13 -0
  18. package/dist/cli/commands/collaboration.js +186 -0
  19. package/dist/cli/commands/continuity.d.ts +29 -0
  20. package/dist/cli/commands/continuity.js +133 -0
  21. package/dist/cli/commands/docs.d.ts +12 -0
  22. package/dist/cli/commands/docs.js +234 -0
  23. package/dist/cli/commands/doctor.d.ts +14 -0
  24. package/dist/cli/commands/doctor.js +57 -0
  25. package/dist/cli/commands/host.d.ts +17 -0
  26. package/dist/cli/commands/host.js +199 -0
  27. package/dist/cli/commands/init.d.ts +59 -0
  28. package/dist/cli/commands/init.js +845 -0
  29. package/dist/cli/commands/runtime.d.ts +14 -0
  30. package/dist/cli/commands/runtime.js +263 -0
  31. package/dist/cli/main.d.ts +2 -0
  32. package/dist/cli/main.js +10 -0
  33. package/dist/cli/output.d.ts +4 -0
  34. package/dist/cli/output.js +1 -0
  35. package/dist/cli/run.d.ts +2 -0
  36. package/dist/cli/run.js +749 -0
  37. package/dist/code/budget.d.ts +14 -0
  38. package/dist/code/budget.js +39 -0
  39. package/dist/code/exec.d.ts +23 -0
  40. package/dist/code/exec.js +133 -0
  41. package/dist/code/identity.d.ts +13 -0
  42. package/dist/code/identity.js +214 -0
  43. package/dist/code/index.d.ts +6 -0
  44. package/dist/code/index.js +3 -0
  45. package/dist/code/providers/codegraph.d.ts +59 -0
  46. package/dist/code/providers/codegraph.js +567 -0
  47. package/dist/code/registration.d.ts +9 -0
  48. package/dist/code/registration.js +293 -0
  49. package/dist/code/service.d.ts +53 -0
  50. package/dist/code/service.js +633 -0
  51. package/dist/code/text.d.ts +3 -0
  52. package/dist/code/text.js +202 -0
  53. package/dist/collab/admission.d.ts +450 -0
  54. package/dist/collab/admission.js +797 -0
  55. package/dist/collab/canonical.d.ts +26 -0
  56. package/dist/collab/canonical.js +136 -0
  57. package/dist/collab/contracts.d.ts +73 -0
  58. package/dist/collab/contracts.js +76 -0
  59. package/dist/collab/envelope.d.ts +52 -0
  60. package/dist/collab/envelope.js +241 -0
  61. package/dist/collab/fingerprint.d.ts +7 -0
  62. package/dist/collab/fingerprint.js +58 -0
  63. package/dist/collab/handoff.d.ts +350 -0
  64. package/dist/collab/handoff.js +513 -0
  65. package/dist/collab/index.d.ts +9 -0
  66. package/dist/collab/index.js +8 -0
  67. package/dist/collab/lifecycle.d.ts +449 -0
  68. package/dist/collab/lifecycle.js +534 -0
  69. package/dist/collab/messages.d.ts +148 -0
  70. package/dist/collab/messages.js +583 -0
  71. package/dist/collab/notify.d.ts +47 -0
  72. package/dist/collab/notify.js +140 -0
  73. package/dist/collab/preflight.d.ts +31 -0
  74. package/dist/collab/preflight.js +271 -0
  75. package/dist/collab/resources.d.ts +119 -0
  76. package/dist/collab/resources.js +451 -0
  77. package/dist/collab/store.d.ts +143 -0
  78. package/dist/collab/store.js +606 -0
  79. package/dist/collab/types.d.ts +354 -0
  80. package/dist/collab/types.js +1 -0
  81. package/dist/collab/validation.d.ts +10 -0
  82. package/dist/collab/validation.js +303 -0
  83. package/dist/continuity/context.d.ts +24 -0
  84. package/dist/continuity/context.js +179 -0
  85. package/dist/continuity/index.d.ts +3 -0
  86. package/dist/continuity/index.js +1 -0
  87. package/dist/continuity/paths.d.ts +37 -0
  88. package/dist/continuity/paths.js +258 -0
  89. package/dist/continuity/service.d.ts +59 -0
  90. package/dist/continuity/service.js +1258 -0
  91. package/dist/hosts/claude/native.d.ts +79 -0
  92. package/dist/hosts/claude/native.js +853 -0
  93. package/dist/hosts/codex/native.d.ts +345 -0
  94. package/dist/hosts/codex/native.js +1528 -0
  95. package/dist/hosts/codex/protocol.d.ts +51 -0
  96. package/dist/hosts/codex/protocol.js +42 -0
  97. package/dist/hosts/detect.d.ts +29 -0
  98. package/dist/hosts/detect.js +73 -0
  99. package/dist/hosts/index.d.ts +10 -0
  100. package/dist/hosts/index.js +10 -0
  101. package/dist/hosts/integration/bundle-export.d.ts +24 -0
  102. package/dist/hosts/integration/bundle-export.js +128 -0
  103. package/dist/hosts/integration/bundle.d.ts +19 -0
  104. package/dist/hosts/integration/bundle.js +99 -0
  105. package/dist/hosts/integration/hook-entry.d.ts +1 -0
  106. package/dist/hosts/integration/hook-entry.js +68 -0
  107. package/dist/hosts/integration/hook.d.ts +32 -0
  108. package/dist/hosts/integration/hook.js +781 -0
  109. package/dist/hosts/integration/pappra-extension-runtime.mjs +24610 -0
  110. package/dist/hosts/integration/pappra-hook.mjs +24728 -0
  111. package/dist/hosts/integration/pi-extension-runtime.d.ts +18 -0
  112. package/dist/hosts/integration/pi-extension-runtime.js +89 -0
  113. package/dist/hosts/integration/pi-template.d.ts +3 -0
  114. package/dist/hosts/integration/pi-template.js +5 -0
  115. package/dist/hosts/integration/service.d.ts +49 -0
  116. package/dist/hosts/integration/service.js +1421 -0
  117. package/dist/hosts/integration/support.d.ts +59 -0
  118. package/dist/hosts/integration/support.js +66 -0
  119. package/dist/hosts/integration/templates.d.ts +22 -0
  120. package/dist/hosts/integration/templates.js +75 -0
  121. package/dist/hosts/pi/protocol.d.ts +22 -0
  122. package/dist/hosts/pi/protocol.js +28 -0
  123. package/dist/hosts/pi/query-result.d.ts +22 -0
  124. package/dist/hosts/pi/query-result.js +710 -0
  125. package/dist/hosts/pi/rpc.d.ts +939 -0
  126. package/dist/hosts/pi/rpc.js +1659 -0
  127. package/dist/hosts/pilot.d.ts +485 -0
  128. package/dist/hosts/pilot.js +593 -0
  129. package/dist/index.d.ts +5 -0
  130. package/dist/index.js +4 -0
  131. package/dist/infra/fs.d.ts +27 -0
  132. package/dist/infra/fs.js +130 -0
  133. package/dist/infra/git.d.ts +60 -0
  134. package/dist/infra/git.js +358 -0
  135. package/dist/infra/owned-process-group.d.ts +173 -0
  136. package/dist/infra/owned-process-group.js +571 -0
  137. package/dist/install/apply.d.ts +65 -0
  138. package/dist/install/apply.js +663 -0
  139. package/dist/install/index.d.ts +5 -0
  140. package/dist/install/index.js +5 -0
  141. package/dist/install/ownership.d.ts +85 -0
  142. package/dist/install/ownership.js +430 -0
  143. package/dist/install/plan.d.ts +39 -0
  144. package/dist/install/plan.js +97 -0
  145. package/dist/install/rollback.d.ts +18 -0
  146. package/dist/install/rollback.js +544 -0
  147. package/dist/install/runtime.d.ts +76 -0
  148. package/dist/install/runtime.js +1252 -0
  149. package/dist/knowledge/catalog.d.ts +47 -0
  150. package/dist/knowledge/catalog.js +308 -0
  151. package/dist/knowledge/context.d.ts +28 -0
  152. package/dist/knowledge/context.js +61 -0
  153. package/dist/knowledge/index.d.ts +3 -0
  154. package/dist/knowledge/index.js +1 -0
  155. package/dist/knowledge/journal.d.ts +16 -0
  156. package/dist/knowledge/journal.js +109 -0
  157. package/dist/knowledge/lifecycle.d.ts +8 -0
  158. package/dist/knowledge/lifecycle.js +1001 -0
  159. package/dist/knowledge/paths.d.ts +37 -0
  160. package/dist/knowledge/paths.js +236 -0
  161. package/dist/knowledge/refs.d.ts +36 -0
  162. package/dist/knowledge/refs.js +267 -0
  163. package/dist/knowledge/service.d.ts +21 -0
  164. package/dist/knowledge/service.js +393 -0
  165. package/dist/knowledge/types.d.ts +171 -0
  166. package/dist/knowledge/types.js +1 -0
  167. package/dist/knowledge/write.d.ts +8 -0
  168. package/dist/knowledge/write.js +1223 -0
  169. package/dist/package-info.d.ts +8 -0
  170. package/dist/package-info.js +25 -0
  171. package/dist/schemas/code.d.ts +377 -0
  172. package/dist/schemas/code.js +145 -0
  173. package/dist/schemas/collaboration.d.ts +115 -0
  174. package/dist/schemas/collaboration.js +106 -0
  175. package/dist/schemas/continuity.d.ts +315 -0
  176. package/dist/schemas/continuity.js +199 -0
  177. package/dist/schemas/diagnostics.d.ts +28 -0
  178. package/dist/schemas/diagnostics.js +27 -0
  179. package/dist/schemas/doctor.d.ts +157 -0
  180. package/dist/schemas/doctor.js +75 -0
  181. package/dist/schemas/document.d.ts +644 -0
  182. package/dist/schemas/document.js +296 -0
  183. package/dist/schemas/errors.d.ts +177 -0
  184. package/dist/schemas/errors.js +176 -0
  185. package/dist/schemas/evidence.d.ts +24 -0
  186. package/dist/schemas/evidence.js +16 -0
  187. package/dist/schemas/host.d.ts +436 -0
  188. package/dist/schemas/host.js +203 -0
  189. package/dist/schemas/index.d.ts +15 -0
  190. package/dist/schemas/index.js +15 -0
  191. package/dist/schemas/init.d.ts +121 -0
  192. package/dist/schemas/init.js +94 -0
  193. package/dist/schemas/install.d.ts +227 -0
  194. package/dist/schemas/install.js +147 -0
  195. package/dist/schemas/machine.d.ts +43 -0
  196. package/dist/schemas/machine.js +96 -0
  197. package/dist/schemas/outcome.d.ts +23 -0
  198. package/dist/schemas/outcome.js +17 -0
  199. package/dist/schemas/paths.d.ts +19 -0
  200. package/dist/schemas/paths.js +63 -0
  201. package/dist/schemas/workspace.d.ts +120 -0
  202. package/dist/schemas/workspace.js +201 -0
  203. package/dist/workspace/config-file.d.ts +36 -0
  204. package/dist/workspace/config-file.js +237 -0
  205. package/dist/workspace/config.d.ts +28 -0
  206. package/dist/workspace/config.js +79 -0
  207. package/dist/workspace/discover.d.ts +34 -0
  208. package/dist/workspace/discover.js +195 -0
  209. package/dist/workspace/documents.d.ts +21 -0
  210. package/dist/workspace/documents.js +395 -0
  211. package/dist/workspace/ignore.d.ts +10 -0
  212. package/dist/workspace/ignore.js +48 -0
  213. package/dist/workspace/index.d.ts +7 -0
  214. package/dist/workspace/index.js +3 -0
  215. package/dist/workspace/repositories.d.ts +24 -0
  216. package/dist/workspace/repositories.js +411 -0
  217. package/dist/workspace/resolve.d.ts +24 -0
  218. package/dist/workspace/resolve.js +268 -0
  219. package/dist/workspace/types.d.ts +71 -0
  220. package/dist/workspace/types.js +1 -0
  221. package/dist/workspace/validation.d.ts +18 -0
  222. package/dist/workspace/validation.js +197 -0
  223. package/package.json +111 -4
  224. package/skills/pappra/SKILL.md +71 -0
  225. package/skills/pappra/references/hosts.md +40 -0
  226. package/skills/pappra/references/lifecycle.md +41 -0
  227. package/skills/pappra/references/operations.md +27 -0
@@ -0,0 +1,268 @@
1
+ import { dirname, join, resolve } from 'node:path';
2
+ import { createDiagnostic } from '../schemas/diagnostics.js';
3
+ import { diagnosticCodes } from '../schemas/errors.js';
4
+ import { isPathInside } from '../schemas/paths.js';
5
+ import { observePath, resolveRealpath } from '../infra/fs.js';
6
+ /** Directory-container check that also accepts a symlinked .pappra inside root. */
7
+ async function usablePappraDirectory(root, path) {
8
+ const real = await resolveRealpath(path);
9
+ return real !== null && isPathInside(root, real);
10
+ }
11
+ /**
12
+ * Walks from an explicit start path (default cwd) to the filesystem root and
13
+ * returns the nearest registered workspace. It only probes `.pappra` markers,
14
+ * never scans home or unrelated project content.
15
+ *
16
+ * A `.pappra` directory without workspace.json stops the walk: a partial
17
+ * registration is reported instead of silently falling back to an outer
18
+ * workspace.
19
+ */
20
+ export async function resolveWorkspaceRoot(options = {}) {
21
+ const requestedPath = options.startPath ?? process.cwd();
22
+ const absolute = resolve(requestedPath);
23
+ const diagnostics = [];
24
+ let observation;
25
+ try {
26
+ observation = await observePath(absolute);
27
+ }
28
+ catch (error) {
29
+ diagnostics.push(createDiagnostic({
30
+ code: diagnosticCodes.workspaceStartInvalid,
31
+ severity: 'error',
32
+ message: `Cannot inspect the workspace start path: ${absolute} (${error instanceof Error ? error.message : String(error)})`,
33
+ source: absolute,
34
+ hint: 'Pass an existing directory path.',
35
+ }));
36
+ return {
37
+ requestedPath,
38
+ startDirectory: null,
39
+ state: 'unavailable',
40
+ root: null,
41
+ configPath: null,
42
+ diagnostics,
43
+ };
44
+ }
45
+ if (observation.kind === 'missing') {
46
+ diagnostics.push(createDiagnostic({
47
+ code: diagnosticCodes.workspaceStartInvalid,
48
+ severity: 'error',
49
+ message: `Workspace start path does not exist: ${absolute}`,
50
+ source: absolute,
51
+ hint: 'Pass an existing directory path.',
52
+ }));
53
+ return {
54
+ requestedPath,
55
+ startDirectory: null,
56
+ state: 'unavailable',
57
+ root: null,
58
+ configPath: null,
59
+ diagnostics,
60
+ };
61
+ }
62
+ if (observation.kind === 'other') {
63
+ diagnostics.push(createDiagnostic({
64
+ code: diagnosticCodes.workspaceStartInvalid,
65
+ severity: 'error',
66
+ message: `Workspace start path is not a directory or regular file: ${absolute}`,
67
+ source: absolute,
68
+ hint: 'Pass an existing directory path.',
69
+ }));
70
+ return {
71
+ requestedPath,
72
+ startDirectory: null,
73
+ state: 'unavailable',
74
+ root: null,
75
+ configPath: null,
76
+ diagnostics,
77
+ };
78
+ }
79
+ let directoryCandidate = absolute;
80
+ if (observation.kind === 'file') {
81
+ directoryCandidate = dirname(absolute);
82
+ }
83
+ else if (observation.kind === 'symlink') {
84
+ const target = await resolveRealpath(absolute);
85
+ const targetObservation = target === null ? null : await observePath(target);
86
+ if (target === null || targetObservation === null) {
87
+ diagnostics.push(createDiagnostic({
88
+ code: diagnosticCodes.workspaceStartInvalid,
89
+ severity: 'error',
90
+ message: `Cannot resolve the symlinked workspace start path: ${absolute}`,
91
+ source: absolute,
92
+ hint: 'Pass an existing readable directory path.',
93
+ }));
94
+ return {
95
+ requestedPath,
96
+ startDirectory: null,
97
+ state: 'unavailable',
98
+ root: null,
99
+ configPath: null,
100
+ diagnostics,
101
+ };
102
+ }
103
+ directoryCandidate =
104
+ targetObservation.kind === 'file' ? dirname(target) : target;
105
+ }
106
+ const startDirectory = await resolveRealpath(directoryCandidate);
107
+ if (startDirectory === null) {
108
+ diagnostics.push(createDiagnostic({
109
+ code: diagnosticCodes.workspaceStartInvalid,
110
+ severity: 'error',
111
+ message: `Cannot resolve the workspace start path: ${absolute}`,
112
+ source: absolute,
113
+ hint: 'Pass an existing readable directory path.',
114
+ }));
115
+ return {
116
+ requestedPath,
117
+ startDirectory: null,
118
+ state: 'unavailable',
119
+ root: null,
120
+ configPath: null,
121
+ diagnostics,
122
+ };
123
+ }
124
+ for (let directory = startDirectory;; directory = dirname(directory)) {
125
+ const pappraPath = join(directory, '.pappra');
126
+ const pappraObservation = await observePath(pappraPath);
127
+ if (pappraObservation.kind !== 'missing') {
128
+ const usable = pappraObservation.kind === 'directory' ||
129
+ (pappraObservation.kind === 'symlink' &&
130
+ (await usablePappraDirectory(directory, pappraPath)));
131
+ if (!usable) {
132
+ diagnostics.push(createDiagnostic({
133
+ code: diagnosticCodes.workspaceRegistrationIncomplete,
134
+ severity: 'error',
135
+ message: `Found ${pappraPath}, but it is not a usable .pappra directory inside the workspace.`,
136
+ source: pappraPath,
137
+ hint: 'Repair or remove the marker explicitly; Pappra does not fall back to an outer workspace.',
138
+ }));
139
+ return {
140
+ requestedPath,
141
+ startDirectory,
142
+ state: 'incomplete',
143
+ root: directory,
144
+ configPath: null,
145
+ diagnostics,
146
+ };
147
+ }
148
+ const configPath = join(pappraPath, 'workspace.json');
149
+ const configObservation = await observePath(configPath);
150
+ if (configObservation.kind === 'missing') {
151
+ diagnostics.push(createDiagnostic({
152
+ code: diagnosticCodes.workspaceRegistrationIncomplete,
153
+ severity: 'error',
154
+ message: `Workspace marker is incomplete: ${configPath} is missing.`,
155
+ source: pappraPath,
156
+ hint: 'Run pappra init or restore workspace.json. Pappra does not silently use an outer workspace.',
157
+ }));
158
+ return {
159
+ requestedPath,
160
+ startDirectory,
161
+ state: 'incomplete',
162
+ root: directory,
163
+ configPath: null,
164
+ diagnostics,
165
+ };
166
+ }
167
+ const configRealpath = await resolveRealpath(configPath);
168
+ if (configObservation.kind === 'symlink' && configRealpath === null) {
169
+ diagnostics.push(createDiagnostic({
170
+ code: diagnosticCodes.workspaceRegistrationIncomplete,
171
+ severity: 'error',
172
+ message: `workspace.json is a broken symlink: ${configPath}`,
173
+ source: configPath,
174
+ hint: 'Restore a readable regular workspace.json; Pappra does not follow broken links.',
175
+ }));
176
+ return {
177
+ requestedPath,
178
+ startDirectory,
179
+ state: 'incomplete',
180
+ root: directory,
181
+ configPath: null,
182
+ diagnostics,
183
+ };
184
+ }
185
+ if (configRealpath !== null && !isPathInside(directory, configRealpath)) {
186
+ diagnostics.push(createDiagnostic({
187
+ code: diagnosticCodes.configSymlinkEscape,
188
+ severity: 'error',
189
+ message: `workspace.json resolves outside the workspace: ${configRealpath}`,
190
+ source: configPath,
191
+ hint: 'Keep configuration inside this workspace; external symlinks are not followed.',
192
+ }));
193
+ return {
194
+ requestedPath,
195
+ startDirectory,
196
+ state: 'incomplete',
197
+ root: directory,
198
+ configPath: null,
199
+ diagnostics,
200
+ };
201
+ }
202
+ if (configObservation.kind === 'symlink' && configRealpath !== null) {
203
+ const targetObservation = await observePath(configRealpath);
204
+ if (targetObservation.kind !== 'file') {
205
+ diagnostics.push(createDiagnostic({
206
+ code: diagnosticCodes.workspaceRegistrationIncomplete,
207
+ severity: 'error',
208
+ message: `workspace.json symlink does not resolve to a regular file: ${configPath}`,
209
+ source: configPath,
210
+ hint: 'Replace it with a readable regular file; Pappra does not guess configuration.',
211
+ }));
212
+ return {
213
+ requestedPath,
214
+ startDirectory,
215
+ state: 'incomplete',
216
+ root: directory,
217
+ configPath: null,
218
+ diagnostics,
219
+ };
220
+ }
221
+ }
222
+ if (configObservation.kind !== 'file' &&
223
+ configObservation.kind !== 'symlink') {
224
+ diagnostics.push(createDiagnostic({
225
+ code: diagnosticCodes.workspaceRegistrationIncomplete,
226
+ severity: 'error',
227
+ message: `workspace.json is not a regular file: ${configPath}`,
228
+ source: configPath,
229
+ hint: 'Replace it with a regular JSON file; Pappra does not guess configuration.',
230
+ }));
231
+ return {
232
+ requestedPath,
233
+ startDirectory,
234
+ state: 'incomplete',
235
+ root: directory,
236
+ configPath: null,
237
+ diagnostics,
238
+ };
239
+ }
240
+ return {
241
+ requestedPath,
242
+ startDirectory,
243
+ state: 'registered',
244
+ root: directory,
245
+ configPath,
246
+ diagnostics,
247
+ };
248
+ }
249
+ const parent = dirname(directory);
250
+ if (parent === directory)
251
+ break;
252
+ }
253
+ diagnostics.push(createDiagnostic({
254
+ code: diagnosticCodes.workspaceNotFound,
255
+ severity: 'info',
256
+ message: `No registered Pappra workspace found above ${startDirectory}.`,
257
+ source: startDirectory,
258
+ hint: 'Provide an explicit workspace root or run pappra init. Pappra does not scan the home directory for workspaces.',
259
+ }));
260
+ return {
261
+ requestedPath,
262
+ startDirectory,
263
+ state: 'missing',
264
+ root: null,
265
+ configPath: null,
266
+ diagnostics,
267
+ };
268
+ }
@@ -0,0 +1,71 @@
1
+ import type { Diagnostic } from '../schemas/diagnostics.js';
2
+ import type { RepositoryAccess } from '../schemas/workspace.js';
3
+ import type { GitRepositoryKind, GitRepositoryObservation } from '../infra/git.js';
4
+ export type WorkspaceConfigState = 'valid' | 'missing' | 'invalid';
5
+ export type MachineConfigState = 'valid' | 'missing' | 'invalid';
6
+ export interface DiscoveredRepository {
7
+ /** Logical id from workspace.json; null for a repository found by scan. */
8
+ readonly id: string | null;
9
+ readonly origin: 'workspace-config' | 'scan';
10
+ readonly external: boolean;
11
+ readonly access: RepositoryAccess;
12
+ /** Real absolute checkout path. External mappings may be outside the root. */
13
+ readonly root: string;
14
+ /** Workspace-relative POSIX path; null for external or out-of-root checkouts. */
15
+ readonly relativePath: string | null;
16
+ /** Original workspace.json path, kept for provenance. */
17
+ readonly configuredPath: string | null;
18
+ readonly git: GitRepositoryObservation;
19
+ readonly kind: GitRepositoryKind;
20
+ readonly diagnostics: readonly Diagnostic[];
21
+ }
22
+ export interface UnresolvedRepository {
23
+ readonly id: string;
24
+ readonly configuredPath: string;
25
+ readonly diagnostics: readonly Diagnostic[];
26
+ }
27
+ export type DocumentKind = 'rule' | 'documentation' | 'decision-record';
28
+ export type DocumentOrigin = 'workspace-config' | 'well-known' | 'directory-scan' | 'repository-scan';
29
+ export interface DiscoveredDocument {
30
+ /** Absolute real path; the file was not moved, renamed, or rewritten. */
31
+ readonly path: string;
32
+ readonly workspaceRelativePath: string | null;
33
+ readonly repositoryId: string | null;
34
+ readonly kind: DocumentKind;
35
+ readonly origin: DocumentOrigin;
36
+ /** Config pointer, well-known marker, or scanned directory that produced this entry. */
37
+ readonly declaredBy: string;
38
+ readonly sizeBytes: number;
39
+ }
40
+ export interface ValidationCandidate {
41
+ readonly id: string;
42
+ readonly origin: 'workspace-config' | 'package-script';
43
+ readonly repositoryId: string | null;
44
+ readonly repositoryAccess: RepositoryAccess | null;
45
+ readonly executable: string;
46
+ readonly args: readonly string[];
47
+ readonly workingDirectory: string | null;
48
+ readonly declaredBy: string;
49
+ /** How the package runner was chosen; never evidence that it is installed. */
50
+ readonly runnerSource: 'workspace-config' | 'declared' | 'lockfile' | 'fallback';
51
+ /**
52
+ * Discovery never resolves, executes, or trusts these commands. A candidate
53
+ * is a declaration only, not evidence that the tool or script is usable.
54
+ */
55
+ readonly verification: 'not-verified';
56
+ }
57
+ export interface DiscoveryOptions {
58
+ /** Directory depth below the workspace root; 0 inspects only the root. */
59
+ readonly scanDepth?: number;
60
+ readonly maxDirectories?: number;
61
+ readonly maxDocuments?: number;
62
+ readonly maxEntriesPerDirectory?: number;
63
+ readonly gitTimeoutMs?: number;
64
+ }
65
+ export interface NormalizedDiscoveryOptions {
66
+ readonly scanDepth: number;
67
+ readonly maxDirectories: number;
68
+ readonly maxDocuments: number;
69
+ readonly maxEntriesPerDirectory: number;
70
+ readonly gitTimeoutMs: number;
71
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
1
+ import type { Diagnostic } from '../schemas/diagnostics.js';
2
+ import type { WorkspaceConfig } from '../schemas/workspace.js';
3
+ import type { DiscoveredRepository, ValidationCandidate } from './types.js';
4
+ export interface ValidationDiscoveryInput {
5
+ readonly root: string;
6
+ readonly config: WorkspaceConfig | null;
7
+ readonly configPath: string;
8
+ readonly repositories: readonly DiscoveredRepository[];
9
+ }
10
+ export interface ValidationDiscoveryOutput {
11
+ readonly candidates: readonly ValidationCandidate[];
12
+ readonly diagnostics: readonly Diagnostic[];
13
+ }
14
+ /**
15
+ * Reports validation commands declared in workspace.json and package.json.
16
+ * Nothing is resolved on PATH or executed.
17
+ */
18
+ export declare function discoverValidationCandidates(input: ValidationDiscoveryInput): Promise<ValidationDiscoveryOutput>;
@@ -0,0 +1,197 @@
1
+ import { join } from 'node:path';
2
+ import { z } from 'zod';
3
+ import { createDiagnostic, pointer } from '../schemas/diagnostics.js';
4
+ import { diagnosticCodes } from '../schemas/errors.js';
5
+ import { observePath, readTextFileBounded } from '../infra/fs.js';
6
+ const packageManifestSchema = z.object({
7
+ packageManager: z.string().min(1).max(128).optional(),
8
+ scripts: z.record(z.string(), z.string()).optional(),
9
+ });
10
+ const knownRunners = new Set(['pnpm', 'npm', 'yarn', 'bun']);
11
+ const validationScriptPattern = /^(?:test|lint|typecheck|check|verify|validate)(?::|$)/;
12
+ function declaredRunner(packageManager) {
13
+ if (packageManager === undefined)
14
+ return null;
15
+ const name = packageManager.split('@')[0] ?? '';
16
+ if (!knownRunners.has(name))
17
+ return 'unsupported';
18
+ return { name, source: 'declared' };
19
+ }
20
+ async function detectRunner(directory, packageManager, diagnostics, manifestPath) {
21
+ const declared = declaredRunner(packageManager);
22
+ if (declared === 'unsupported') {
23
+ diagnostics.push(createDiagnostic({
24
+ code: diagnosticCodes.validationRunnerUnsupported,
25
+ severity: 'warning',
26
+ message: `Declared package manager "${packageManager}" is not supported for validation candidates.`,
27
+ source: manifestPath,
28
+ pointer: pointer('packageManager'),
29
+ hint: 'Pappra reports no package-script candidates instead of guessing a different runner.',
30
+ }));
31
+ return null;
32
+ }
33
+ if (declared !== null)
34
+ return declared;
35
+ const candidates = [
36
+ ['pnpm-lock.yaml', 'pnpm'],
37
+ ['pnpm-workspace.yaml', 'pnpm'],
38
+ ['yarn.lock', 'yarn'],
39
+ ['bun.lock', 'bun'],
40
+ ['bun.lockb', 'bun'],
41
+ ['package-lock.json', 'npm'],
42
+ ];
43
+ const found = [];
44
+ for (const [file, runner] of candidates) {
45
+ const observation = await observePath(join(directory, file));
46
+ if (observation.kind === 'file' && !found.includes(runner)) {
47
+ found.push(runner);
48
+ }
49
+ }
50
+ if (found.length > 1) {
51
+ diagnostics.push(createDiagnostic({
52
+ code: diagnosticCodes.validationRunnerAmbiguous,
53
+ severity: 'warning',
54
+ message: `Multiple package-manager lockfiles were found: ${found.join(', ')}.`,
55
+ source: manifestPath,
56
+ hint: `Discovery used ${found[0] ?? 'npm'} without executing any script; declare packageManager to remove the ambiguity.`,
57
+ }));
58
+ }
59
+ const selected = found[0];
60
+ return selected === undefined
61
+ ? { name: 'npm', source: 'fallback' }
62
+ : { name: selected, source: 'lockfile' };
63
+ }
64
+ /**
65
+ * Reports validation commands declared in workspace.json and package.json.
66
+ * Nothing is resolved on PATH or executed.
67
+ */
68
+ export async function discoverValidationCandidates(input) {
69
+ const { root, config, configPath, repositories } = input;
70
+ const candidates = [];
71
+ const diagnostics = [];
72
+ if (config !== null) {
73
+ for (const [index, validation] of config.validation.entries()) {
74
+ const repository = repositories.find((item) => item.id === validation.repositoryId);
75
+ if (repository === undefined) {
76
+ diagnostics.push(createDiagnostic({
77
+ code: diagnosticCodes.validationCandidateUnbound,
78
+ severity: 'warning',
79
+ message: `Validation "${validation.id}" is declared for repository "${validation.repositoryId}", but that checkout was not discovered.`,
80
+ source: configPath,
81
+ pointer: pointer('validation', index, 'repositoryId'),
82
+ hint: 'Fix the repository path or mapping before treating this command as runnable.',
83
+ }));
84
+ }
85
+ candidates.push({
86
+ id: `workspace-config:${validation.id}`,
87
+ origin: 'workspace-config',
88
+ repositoryId: validation.repositoryId,
89
+ repositoryAccess: repository?.access ?? null,
90
+ executable: validation.executable,
91
+ args: validation.args,
92
+ workingDirectory: repository?.root ?? null,
93
+ declaredBy: `${configPath}#${pointer('validation', index)}`,
94
+ runnerSource: 'workspace-config',
95
+ verification: 'not-verified',
96
+ });
97
+ }
98
+ }
99
+ const manifestDirectories = new Map();
100
+ for (const repository of repositories) {
101
+ manifestDirectories.set(repository.root, repository);
102
+ }
103
+ if (!manifestDirectories.has(root)) {
104
+ manifestDirectories.set(root, null);
105
+ }
106
+ for (const [directory, repository] of manifestDirectories) {
107
+ const manifestPath = join(directory, 'package.json');
108
+ const observation = await observePath(manifestPath);
109
+ if (observation.kind === 'missing')
110
+ continue;
111
+ if (observation.kind !== 'file') {
112
+ diagnostics.push(createDiagnostic({
113
+ code: diagnosticCodes.packageManifestInvalid,
114
+ severity: 'warning',
115
+ message: `package.json is not a regular file: ${manifestPath}`,
116
+ source: manifestPath,
117
+ hint: 'Discovery skips this manifest and executes nothing.',
118
+ }));
119
+ continue;
120
+ }
121
+ const read = await readTextFileBounded(manifestPath, 1024 * 1024);
122
+ if (!read.ok) {
123
+ diagnostics.push(createDiagnostic({
124
+ code: diagnosticCodes.packageManifestInvalid,
125
+ severity: 'warning',
126
+ message: `Cannot read package.json: ${read.message}`,
127
+ source: manifestPath,
128
+ hint: 'Fix the manifest or permissions; no scripts were executed.',
129
+ }));
130
+ continue;
131
+ }
132
+ let raw;
133
+ try {
134
+ raw = JSON.parse(read.value.text);
135
+ }
136
+ catch (error) {
137
+ diagnostics.push(createDiagnostic({
138
+ code: diagnosticCodes.packageManifestInvalid,
139
+ severity: 'warning',
140
+ message: `Invalid package.json: ${error instanceof Error ? error.message : String(error)}`,
141
+ source: manifestPath,
142
+ hint: 'Fix the manifest; discovery did not execute or rewrite it.',
143
+ }));
144
+ continue;
145
+ }
146
+ const parsed = packageManifestSchema.safeParse(raw);
147
+ if (!parsed.success) {
148
+ const firstIssue = parsed.error.issues[0];
149
+ diagnostics.push(createDiagnostic({
150
+ code: diagnosticCodes.packageManifestInvalid,
151
+ severity: 'warning',
152
+ message: `package.json has an unexpected shape: ${firstIssue?.message ?? 'validation failed'}`,
153
+ source: manifestPath,
154
+ pointer: firstIssue === undefined
155
+ ? null
156
+ : pointer(...firstIssue.path.map((item) => String(item))),
157
+ hint: 'Discovery skipped this manifest; no scripts were executed.',
158
+ }));
159
+ continue;
160
+ }
161
+ const scripts = parsed.data.scripts;
162
+ if (scripts === undefined)
163
+ continue;
164
+ const runner = await detectRunner(directory, parsed.data.packageManager, diagnostics, manifestPath);
165
+ if (runner === null)
166
+ continue;
167
+ const scriptNames = Object.keys(scripts)
168
+ .filter((name) => validationScriptPattern.test(name))
169
+ .sort((left, right) => left.localeCompare(right));
170
+ for (const name of scriptNames) {
171
+ if (candidates.length >= 256) {
172
+ diagnostics.push(createDiagnostic({
173
+ code: diagnosticCodes.discoveryLimitReached,
174
+ severity: 'warning',
175
+ message: 'Validation candidate list reached its 256-entry bound.',
176
+ source: manifestPath,
177
+ hint: 'Declare only the validation commands needed for this workspace.',
178
+ }));
179
+ break;
180
+ }
181
+ const scope = repository?.id ?? repository?.relativePath ?? 'workspace';
182
+ candidates.push({
183
+ id: `package-script:${scope}:${name}`,
184
+ origin: 'package-script',
185
+ repositoryId: repository?.id ?? null,
186
+ repositoryAccess: repository?.access ?? null,
187
+ executable: runner.name,
188
+ args: ['run', name],
189
+ workingDirectory: directory,
190
+ declaredBy: `${manifestPath}#${pointer('scripts', name)}`,
191
+ runnerSource: runner.source,
192
+ verification: 'not-verified',
193
+ });
194
+ }
195
+ }
196
+ return { candidates, diagnostics };
197
+ }
package/package.json CHANGED
@@ -1,6 +1,113 @@
1
1
  {
2
2
  "name": "@phyzess/pappra",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0-beta.10",
4
+ "description": "Portable workspace knowledge and continuity for coding agents.",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "license": "MIT",
9
+ "type": "module",
10
+ "engines": {
11
+ "node": ">=24"
12
+ },
13
+ "packageManager": "pnpm@12.3.4",
14
+ "bin": {
15
+ "pappra": "dist/cli/main.js"
16
+ },
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js"
21
+ },
22
+ "./collab": {
23
+ "types": "./dist/collab/index.d.ts",
24
+ "import": "./dist/collab/index.js"
25
+ },
26
+ "./collab/admission": {
27
+ "types": "./dist/collab/admission.d.ts",
28
+ "import": "./dist/collab/admission.js"
29
+ },
30
+ "./hosts": {
31
+ "types": "./dist/hosts/index.d.ts",
32
+ "import": "./dist/hosts/index.js"
33
+ },
34
+ "./doctor": {
35
+ "types": "./dist/app/doctor.d.ts",
36
+ "import": "./dist/app/doctor.js"
37
+ },
38
+ "./init": {
39
+ "types": "./dist/app/init.d.ts",
40
+ "import": "./dist/app/init.js"
41
+ },
42
+ "./collaboration": {
43
+ "types": "./dist/app/collaboration.d.ts",
44
+ "import": "./dist/app/collaboration.js"
45
+ },
46
+ "./install": {
47
+ "types": "./dist/install/index.d.ts",
48
+ "import": "./dist/install/index.js"
49
+ },
50
+ "./continuity": {
51
+ "types": "./dist/continuity/index.d.ts",
52
+ "import": "./dist/continuity/index.js"
53
+ },
54
+ "./code": {
55
+ "types": "./dist/code/index.d.ts",
56
+ "import": "./dist/code/index.js"
57
+ },
58
+ "./knowledge": {
59
+ "types": "./dist/knowledge/index.d.ts",
60
+ "import": "./dist/knowledge/index.js"
61
+ },
62
+ "./schemas": {
63
+ "types": "./dist/schemas/index.d.ts",
64
+ "import": "./dist/schemas/index.js"
65
+ },
66
+ "./workspace": {
67
+ "types": "./dist/workspace/index.d.ts",
68
+ "import": "./dist/workspace/index.js"
69
+ },
70
+ "./package.json": "./package.json"
71
+ },
72
+ "files": [
73
+ "dist",
74
+ "assets",
75
+ "skills",
76
+ "README.md",
77
+ "THIRD_PARTY_NOTICES.md"
78
+ ],
79
+ "scripts": {
80
+ "build:scripts": "tsc -p tsconfig.scripts.json",
81
+ "build": "tsc -p tsconfig.scripts.json && node .build/scripts/scripts/clean-build.js && tsc -p tsconfig.build.json && node .build/scripts/scripts/build-assets.js",
82
+ "typecheck": "tsc -p tsconfig.build.json --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.scripts.json --noEmit",
83
+ "pretest": "pnpm build && tsc -p tsconfig.test.json",
84
+ "test": "node .build/test/scripts/run-tests.js",
85
+ "format": "prettier --write .",
86
+ "format:check": "prettier --check .",
87
+ "source:check": "pnpm build:scripts && node .build/scripts/scripts/check-source-policy.js",
88
+ "package:check": "pnpm build && node .build/scripts/scripts/verify-package.js",
89
+ "check": "pnpm source:check && pnpm typecheck && pnpm format:check && pnpm test && pnpm package:check",
90
+ "prepare": "npm run build",
91
+ "prepack": "npm run build"
92
+ },
93
+ "devDependencies": {
94
+ "@types/node": "24.19.1",
95
+ "esbuild": "^0.28.2",
96
+ "prettier": "3.9.9",
97
+ "typescript": "7.0.2"
98
+ },
99
+ "dependencies": {
100
+ "@clack/prompts": "1.8.1",
101
+ "zod": "4.6.6"
102
+ },
103
+ "repository": {
104
+ "type": "git",
105
+ "url": "https://github.com/phyzess/pappra.git"
106
+ },
107
+ "homepage": "https://github.com/phyzess/pappra",
108
+ "pnpm": {
109
+ "onlyBuiltDependencies": [
110
+ "esbuild"
111
+ ]
112
+ }
113
+ }