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

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 (221) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -2
  3. package/THIRD_PARTY_NOTICES.md +16 -0
  4. package/assets/integrations/skills/pappra/SKILL.md +68 -0
  5. package/assets/integrations/skills/pappra/references/hosts.md +30 -0
  6. package/assets/integrations/skills/pappra/references/lifecycle.md +37 -0
  7. package/assets/integrations/skills/pappra/references/operations.md +14 -0
  8. package/assets/manifest.json +4 -0
  9. package/dist/app/collaboration.d.ts +170 -0
  10. package/dist/app/collaboration.js +1650 -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 +143 -0
  14. package/dist/app/init.js +1111 -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 +11 -0
  22. package/dist/cli/commands/docs.js +133 -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 +13 -0
  26. package/dist/cli/commands/host.js +125 -0
  27. package/dist/cli/commands/init.d.ts +51 -0
  28. package/dist/cli/commands/init.js +668 -0
  29. package/dist/cli/main.d.ts +2 -0
  30. package/dist/cli/main.js +10 -0
  31. package/dist/cli/output.d.ts +4 -0
  32. package/dist/cli/output.js +1 -0
  33. package/dist/cli/run.d.ts +2 -0
  34. package/dist/cli/run.js +597 -0
  35. package/dist/code/budget.d.ts +14 -0
  36. package/dist/code/budget.js +39 -0
  37. package/dist/code/exec.d.ts +23 -0
  38. package/dist/code/exec.js +133 -0
  39. package/dist/code/identity.d.ts +13 -0
  40. package/dist/code/identity.js +214 -0
  41. package/dist/code/index.d.ts +6 -0
  42. package/dist/code/index.js +3 -0
  43. package/dist/code/providers/codegraph.d.ts +59 -0
  44. package/dist/code/providers/codegraph.js +567 -0
  45. package/dist/code/registration.d.ts +9 -0
  46. package/dist/code/registration.js +293 -0
  47. package/dist/code/service.d.ts +53 -0
  48. package/dist/code/service.js +633 -0
  49. package/dist/code/text.d.ts +3 -0
  50. package/dist/code/text.js +202 -0
  51. package/dist/collab/admission.d.ts +450 -0
  52. package/dist/collab/admission.js +797 -0
  53. package/dist/collab/canonical.d.ts +26 -0
  54. package/dist/collab/canonical.js +136 -0
  55. package/dist/collab/contracts.d.ts +73 -0
  56. package/dist/collab/contracts.js +76 -0
  57. package/dist/collab/envelope.d.ts +52 -0
  58. package/dist/collab/envelope.js +241 -0
  59. package/dist/collab/fingerprint.d.ts +7 -0
  60. package/dist/collab/fingerprint.js +58 -0
  61. package/dist/collab/handoff.d.ts +350 -0
  62. package/dist/collab/handoff.js +513 -0
  63. package/dist/collab/index.d.ts +9 -0
  64. package/dist/collab/index.js +8 -0
  65. package/dist/collab/lifecycle.d.ts +449 -0
  66. package/dist/collab/lifecycle.js +534 -0
  67. package/dist/collab/messages.d.ts +148 -0
  68. package/dist/collab/messages.js +583 -0
  69. package/dist/collab/notify.d.ts +47 -0
  70. package/dist/collab/notify.js +140 -0
  71. package/dist/collab/preflight.d.ts +31 -0
  72. package/dist/collab/preflight.js +271 -0
  73. package/dist/collab/resources.d.ts +119 -0
  74. package/dist/collab/resources.js +451 -0
  75. package/dist/collab/store.d.ts +143 -0
  76. package/dist/collab/store.js +606 -0
  77. package/dist/collab/types.d.ts +354 -0
  78. package/dist/collab/types.js +1 -0
  79. package/dist/collab/validation.d.ts +10 -0
  80. package/dist/collab/validation.js +303 -0
  81. package/dist/continuity/context.d.ts +24 -0
  82. package/dist/continuity/context.js +179 -0
  83. package/dist/continuity/index.d.ts +3 -0
  84. package/dist/continuity/index.js +1 -0
  85. package/dist/continuity/paths.d.ts +37 -0
  86. package/dist/continuity/paths.js +258 -0
  87. package/dist/continuity/service.d.ts +59 -0
  88. package/dist/continuity/service.js +1258 -0
  89. package/dist/hosts/claude/native.d.ts +79 -0
  90. package/dist/hosts/claude/native.js +853 -0
  91. package/dist/hosts/codex/native.d.ts +345 -0
  92. package/dist/hosts/codex/native.js +1528 -0
  93. package/dist/hosts/codex/protocol.d.ts +51 -0
  94. package/dist/hosts/codex/protocol.js +42 -0
  95. package/dist/hosts/detect.d.ts +28 -0
  96. package/dist/hosts/detect.js +73 -0
  97. package/dist/hosts/index.d.ts +10 -0
  98. package/dist/hosts/index.js +10 -0
  99. package/dist/hosts/integration/bundle.d.ts +16 -0
  100. package/dist/hosts/integration/bundle.js +81 -0
  101. package/dist/hosts/integration/hook-entry.d.ts +1 -0
  102. package/dist/hosts/integration/hook-entry.js +68 -0
  103. package/dist/hosts/integration/hook.d.ts +32 -0
  104. package/dist/hosts/integration/hook.js +781 -0
  105. package/dist/hosts/integration/pi-extension-runtime.d.ts +18 -0
  106. package/dist/hosts/integration/pi-extension-runtime.js +89 -0
  107. package/dist/hosts/integration/pi-template.d.ts +3 -0
  108. package/dist/hosts/integration/pi-template.js +5 -0
  109. package/dist/hosts/integration/service.d.ts +41 -0
  110. package/dist/hosts/integration/service.js +1381 -0
  111. package/dist/hosts/integration/support.d.ts +36 -0
  112. package/dist/hosts/integration/support.js +41 -0
  113. package/dist/hosts/integration/templates.d.ts +22 -0
  114. package/dist/hosts/integration/templates.js +70 -0
  115. package/dist/hosts/pi/protocol.d.ts +22 -0
  116. package/dist/hosts/pi/protocol.js +28 -0
  117. package/dist/hosts/pi/query-result.d.ts +22 -0
  118. package/dist/hosts/pi/query-result.js +710 -0
  119. package/dist/hosts/pi/rpc.d.ts +937 -0
  120. package/dist/hosts/pi/rpc.js +1621 -0
  121. package/dist/hosts/pilot.d.ts +485 -0
  122. package/dist/hosts/pilot.js +593 -0
  123. package/dist/index.d.ts +5 -0
  124. package/dist/index.js +4 -0
  125. package/dist/infra/fs.d.ts +27 -0
  126. package/dist/infra/fs.js +130 -0
  127. package/dist/infra/git.d.ts +60 -0
  128. package/dist/infra/git.js +358 -0
  129. package/dist/infra/owned-process-group.d.ts +173 -0
  130. package/dist/infra/owned-process-group.js +571 -0
  131. package/dist/install/apply.d.ts +65 -0
  132. package/dist/install/apply.js +663 -0
  133. package/dist/install/index.d.ts +5 -0
  134. package/dist/install/index.js +5 -0
  135. package/dist/install/ownership.d.ts +85 -0
  136. package/dist/install/ownership.js +430 -0
  137. package/dist/install/plan.d.ts +39 -0
  138. package/dist/install/plan.js +97 -0
  139. package/dist/install/rollback.d.ts +18 -0
  140. package/dist/install/rollback.js +544 -0
  141. package/dist/install/runtime.d.ts +76 -0
  142. package/dist/install/runtime.js +1145 -0
  143. package/dist/knowledge/catalog.d.ts +47 -0
  144. package/dist/knowledge/catalog.js +308 -0
  145. package/dist/knowledge/context.d.ts +28 -0
  146. package/dist/knowledge/context.js +61 -0
  147. package/dist/knowledge/index.d.ts +3 -0
  148. package/dist/knowledge/index.js +1 -0
  149. package/dist/knowledge/journal.d.ts +16 -0
  150. package/dist/knowledge/journal.js +109 -0
  151. package/dist/knowledge/lifecycle.d.ts +8 -0
  152. package/dist/knowledge/lifecycle.js +1001 -0
  153. package/dist/knowledge/paths.d.ts +37 -0
  154. package/dist/knowledge/paths.js +236 -0
  155. package/dist/knowledge/refs.d.ts +36 -0
  156. package/dist/knowledge/refs.js +267 -0
  157. package/dist/knowledge/service.d.ts +21 -0
  158. package/dist/knowledge/service.js +393 -0
  159. package/dist/knowledge/types.d.ts +171 -0
  160. package/dist/knowledge/types.js +1 -0
  161. package/dist/knowledge/write.d.ts +8 -0
  162. package/dist/knowledge/write.js +1223 -0
  163. package/dist/package-info.d.ts +6 -0
  164. package/dist/package-info.js +19 -0
  165. package/dist/schemas/code.d.ts +377 -0
  166. package/dist/schemas/code.js +145 -0
  167. package/dist/schemas/collaboration.d.ts +115 -0
  168. package/dist/schemas/collaboration.js +106 -0
  169. package/dist/schemas/continuity.d.ts +315 -0
  170. package/dist/schemas/continuity.js +199 -0
  171. package/dist/schemas/diagnostics.d.ts +28 -0
  172. package/dist/schemas/diagnostics.js +27 -0
  173. package/dist/schemas/doctor.d.ts +157 -0
  174. package/dist/schemas/doctor.js +75 -0
  175. package/dist/schemas/document.d.ts +644 -0
  176. package/dist/schemas/document.js +296 -0
  177. package/dist/schemas/errors.d.ts +177 -0
  178. package/dist/schemas/errors.js +176 -0
  179. package/dist/schemas/evidence.d.ts +24 -0
  180. package/dist/schemas/evidence.js +16 -0
  181. package/dist/schemas/host.d.ts +436 -0
  182. package/dist/schemas/host.js +201 -0
  183. package/dist/schemas/index.d.ts +15 -0
  184. package/dist/schemas/index.js +15 -0
  185. package/dist/schemas/init.d.ts +121 -0
  186. package/dist/schemas/init.js +94 -0
  187. package/dist/schemas/install.d.ts +213 -0
  188. package/dist/schemas/install.js +139 -0
  189. package/dist/schemas/machine.d.ts +43 -0
  190. package/dist/schemas/machine.js +96 -0
  191. package/dist/schemas/outcome.d.ts +23 -0
  192. package/dist/schemas/outcome.js +17 -0
  193. package/dist/schemas/paths.d.ts +19 -0
  194. package/dist/schemas/paths.js +63 -0
  195. package/dist/schemas/workspace.d.ts +120 -0
  196. package/dist/schemas/workspace.js +201 -0
  197. package/dist/workspace/config-file.d.ts +36 -0
  198. package/dist/workspace/config-file.js +237 -0
  199. package/dist/workspace/config.d.ts +28 -0
  200. package/dist/workspace/config.js +79 -0
  201. package/dist/workspace/discover.d.ts +34 -0
  202. package/dist/workspace/discover.js +195 -0
  203. package/dist/workspace/documents.d.ts +21 -0
  204. package/dist/workspace/documents.js +395 -0
  205. package/dist/workspace/ignore.d.ts +10 -0
  206. package/dist/workspace/ignore.js +48 -0
  207. package/dist/workspace/index.d.ts +7 -0
  208. package/dist/workspace/index.js +3 -0
  209. package/dist/workspace/repositories.d.ts +24 -0
  210. package/dist/workspace/repositories.js +411 -0
  211. package/dist/workspace/resolve.d.ts +24 -0
  212. package/dist/workspace/resolve.js +268 -0
  213. package/dist/workspace/types.d.ts +71 -0
  214. package/dist/workspace/types.js +1 -0
  215. package/dist/workspace/validation.d.ts +18 -0
  216. package/dist/workspace/validation.js +197 -0
  217. package/package.json +105 -4
  218. package/skills/pappra/SKILL.md +68 -0
  219. package/skills/pappra/references/hosts.md +30 -0
  220. package/skills/pappra/references/lifecycle.md +37 -0
  221. package/skills/pappra/references/operations.md +14 -0
@@ -0,0 +1,96 @@
1
+ import { isAbsolute } from 'node:path';
2
+ import { z } from 'zod';
3
+ import { createDiagnostic, pointer } from './diagnostics.js';
4
+ import { diagnosticCodes } from './errors.js';
5
+ export const MACHINE_SCHEMA = 'pappra/machine/1';
6
+ const identifierSchema = z
7
+ .string()
8
+ .min(1)
9
+ .max(64)
10
+ .regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/);
11
+ export const hostSchema = z.enum(['pi', 'codex', 'claude']);
12
+ export const externalRepositoryMappingSchema = z.strictObject({
13
+ id: identifierSchema,
14
+ path: z.string().min(1).max(4096),
15
+ });
16
+ export const runtimeLocationSchema = z.strictObject({
17
+ version: z.string().min(1).max(128),
18
+ executable: z.string().min(1).max(4096),
19
+ verifiedAt: z.iso.datetime({ offset: true }),
20
+ });
21
+ export const machineConfigSchema = z.strictObject({
22
+ schema: z.literal(MACHINE_SCHEMA),
23
+ workspaceRealpath: z.string().min(1).max(4096),
24
+ checkoutId: z.uuid(),
25
+ externalRepositories: z.array(externalRepositoryMappingSchema).max(128),
26
+ selectedHosts: z.array(hostSchema).max(16),
27
+ runtime: runtimeLocationSchema.nullable(),
28
+ });
29
+ export function validateMachineConfigSemantics(config, options) {
30
+ const { configPath } = options;
31
+ const diagnostics = [];
32
+ const externalIds = new Map();
33
+ if (!isAbsolute(config.workspaceRealpath)) {
34
+ diagnostics.push(createDiagnostic({
35
+ code: diagnosticCodes.configPathAbsolute,
36
+ severity: 'error',
37
+ message: 'workspaceRealpath must be an absolute host path.',
38
+ source: configPath,
39
+ pointer: pointer('workspaceRealpath'),
40
+ hint: 'Write the real path of this checkout; machine.json is not shared across machines.',
41
+ }));
42
+ }
43
+ config.externalRepositories.forEach((mapping, index) => {
44
+ const first = externalIds.get(mapping.id);
45
+ if (first !== undefined) {
46
+ diagnostics.push(createDiagnostic({
47
+ code: diagnosticCodes.machineExternalDuplicate,
48
+ severity: 'error',
49
+ message: `External repository id "${mapping.id}" is already mapped by externalRepositories[${first}].`,
50
+ source: configPath,
51
+ pointer: pointer('externalRepositories', index, 'id'),
52
+ hint: 'Keep exactly one local mapping per logical external repository id.',
53
+ }));
54
+ }
55
+ else {
56
+ externalIds.set(mapping.id, index);
57
+ }
58
+ if (!isAbsolute(mapping.path) || mapping.path.includes('\0')) {
59
+ diagnostics.push(createDiagnostic({
60
+ code: diagnosticCodes.machineExternalPathInvalid,
61
+ severity: 'error',
62
+ message: `External repository "${mapping.id}" must map to an absolute host path.`,
63
+ source: configPath,
64
+ pointer: pointer('externalRepositories', index, 'path'),
65
+ hint: 'Shared workspace.json stores only external:<id>; the absolute path stays in local machine.json.',
66
+ }));
67
+ }
68
+ });
69
+ const seenHosts = new Set();
70
+ for (const [index, host] of config.selectedHosts.entries()) {
71
+ if (seenHosts.has(host)) {
72
+ diagnostics.push(createDiagnostic({
73
+ code: diagnosticCodes.configSchemaInvalid,
74
+ severity: 'warning',
75
+ message: `Host "${host}" is selected more than once.`,
76
+ source: configPath,
77
+ pointer: pointer('selectedHosts', index),
78
+ hint: 'Remove the duplicate host entry.',
79
+ }));
80
+ }
81
+ seenHosts.add(host);
82
+ }
83
+ if (config.runtime !== null) {
84
+ if (!isAbsolute(config.runtime.executable)) {
85
+ diagnostics.push(createDiagnostic({
86
+ code: diagnosticCodes.configPathAbsolute,
87
+ severity: 'error',
88
+ message: 'The verified runtime executable must be an absolute host path.',
89
+ source: configPath,
90
+ pointer: pointer('runtime', 'executable'),
91
+ hint: 'Record the fixed runtime path created by the installer.',
92
+ }));
93
+ }
94
+ }
95
+ return diagnostics;
96
+ }
@@ -0,0 +1,23 @@
1
+ import type { DiagnosticCode } from './errors.js';
2
+ /** Expected rejection; unexpected exceptions are converted at application boundaries. */
3
+ export type Outcome<T> = {
4
+ readonly ok: true;
5
+ readonly value: T;
6
+ } | {
7
+ readonly ok: false;
8
+ readonly code: DiagnosticCode;
9
+ readonly message: string;
10
+ readonly retryable: boolean;
11
+ readonly nextAction: string | null;
12
+ };
13
+ export interface FailureOptions {
14
+ readonly retryable?: boolean;
15
+ readonly nextAction?: string | null;
16
+ readonly cause?: unknown;
17
+ }
18
+ export type Failure = Extract<Outcome<never>, {
19
+ readonly ok: false;
20
+ }>;
21
+ export declare function succeeded<T>(value: T): Outcome<T>;
22
+ export declare function failed(code: DiagnosticCode, message: string, options?: FailureOptions): Failure;
23
+ export declare function errorMessage(error: unknown): string;
@@ -0,0 +1,17 @@
1
+ export function succeeded(value) {
2
+ return { ok: true, value };
3
+ }
4
+ export function failed(code, message, options = {}) {
5
+ const failure = {
6
+ ok: false,
7
+ code,
8
+ message,
9
+ retryable: options.retryable ?? false,
10
+ nextAction: options.nextAction ?? null,
11
+ ...(options.cause === undefined ? {} : { cause: options.cause }),
12
+ };
13
+ return failure;
14
+ }
15
+ export function errorMessage(error) {
16
+ return error instanceof Error ? error.message : String(error);
17
+ }
@@ -0,0 +1,19 @@
1
+ export declare const externalRepositoryPrefix = "external:";
2
+ export declare function hasNul(value: string): boolean;
3
+ /**
4
+ * Shared workspace paths are portable and relative: forward slashes only, no
5
+ * drive or UNC prefix, and no lexical escape above the configuration root.
6
+ */
7
+ export declare function isPortableRelativePath(value: string): boolean;
8
+ /** Normalizes a portable relative path to a stable, slash-separated form. */
9
+ export declare function normalizePortableRelativePath(value: string): string;
10
+ /** Resolves a portable relative path against a host-native root. */
11
+ export declare function resolvePortableRelativePath(root: string, value: string): string;
12
+ /** Returns a slash-separated path relative to root, or null when outside. */
13
+ export declare function toWorkspaceRelativePath(root: string, target: string): string | null;
14
+ export declare function isPathInside(root: string, target: string): boolean;
15
+ /** Compares already-normalized paths; Windows comparisons are case-insensitive. */
16
+ export declare function samePath(left: string, right: string): boolean;
17
+ export declare function isExternalRepositoryPath(value: string): boolean;
18
+ /** Returns the logical ID in `external:<id>`, or null when malformed. */
19
+ export declare function externalRepositoryId(value: string): string | null;
@@ -0,0 +1,63 @@
1
+ import { isAbsolute, posix, relative, resolve, sep, win32 } from 'node:path';
2
+ export const externalRepositoryPrefix = 'external:';
3
+ const portableIdentifier = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
4
+ export function hasNul(value) {
5
+ return value.includes('\0');
6
+ }
7
+ /**
8
+ * Shared workspace paths are portable and relative: forward slashes only, no
9
+ * drive or UNC prefix, and no lexical escape above the configuration root.
10
+ */
11
+ export function isPortableRelativePath(value) {
12
+ if (value.length === 0 || hasNul(value) || value.includes('\\'))
13
+ return false;
14
+ if (posix.isAbsolute(value) || win32.isAbsolute(value))
15
+ return false;
16
+ const normalized = posix.normalize(value);
17
+ return (normalized !== '..' &&
18
+ !normalized.startsWith('../') &&
19
+ !normalized.startsWith('/'));
20
+ }
21
+ /** Normalizes a portable relative path to a stable, slash-separated form. */
22
+ export function normalizePortableRelativePath(value) {
23
+ const normalized = posix.normalize(value);
24
+ if (normalized === '.')
25
+ return '.';
26
+ return normalized.replace(/\/+$/, '');
27
+ }
28
+ /** Resolves a portable relative path against a host-native root. */
29
+ export function resolvePortableRelativePath(root, value) {
30
+ const normalized = normalizePortableRelativePath(value);
31
+ if (normalized === '.')
32
+ return resolve(root);
33
+ return resolve(root, ...normalized.split('/'));
34
+ }
35
+ /** Returns a slash-separated path relative to root, or null when outside. */
36
+ export function toWorkspaceRelativePath(root, target) {
37
+ if (!isPathInside(root, target))
38
+ return null;
39
+ const path = relative(root, target);
40
+ return path === '' ? '.' : path.split(sep).join('/');
41
+ }
42
+ export function isPathInside(root, target) {
43
+ const path = relative(root, target);
44
+ return (path === '' ||
45
+ (path !== '..' && !path.startsWith(`..${sep}`) && !isAbsolute(path)));
46
+ }
47
+ /** Compares already-normalized paths; Windows comparisons are case-insensitive. */
48
+ export function samePath(left, right) {
49
+ if (process.platform === 'win32') {
50
+ return left.toLowerCase() === right.toLowerCase();
51
+ }
52
+ return left === right;
53
+ }
54
+ export function isExternalRepositoryPath(value) {
55
+ return value.startsWith(externalRepositoryPrefix);
56
+ }
57
+ /** Returns the logical ID in `external:<id>`, or null when malformed. */
58
+ export function externalRepositoryId(value) {
59
+ if (!isExternalRepositoryPath(value))
60
+ return null;
61
+ const id = value.slice(externalRepositoryPrefix.length);
62
+ return portableIdentifier.test(id) ? id : null;
63
+ }
@@ -0,0 +1,120 @@
1
+ import { z } from 'zod';
2
+ import { type Diagnostic } from './diagnostics.js';
3
+ export declare const WORKSPACE_SCHEMA = "pappra/workspace/1";
4
+ export declare const repositoryAccessSchema: z.ZodEnum<{
5
+ read: "read";
6
+ write: "write";
7
+ }>;
8
+ export declare const codeGraphSchema: z.ZodEnum<{
9
+ codegraph: "codegraph";
10
+ none: "none";
11
+ }>;
12
+ export declare const symbolProviderSchema: z.ZodEnum<{
13
+ auto: "auto";
14
+ none: "none";
15
+ serena: "serena";
16
+ }>;
17
+ export declare const indexPreparationSchema: z.ZodEnum<{
18
+ manual: "manual";
19
+ "on-demand": "on-demand";
20
+ }>;
21
+ export declare const taskVisibilitySchema: z.ZodEnum<{
22
+ private: "private";
23
+ shared: "shared";
24
+ }>;
25
+ export declare const workspaceRepositorySchema: z.ZodObject<{
26
+ id: z.ZodString;
27
+ path: z.ZodString;
28
+ access: z.ZodEnum<{
29
+ read: "read";
30
+ write: "write";
31
+ }>;
32
+ }, z.core.$strict>;
33
+ export declare const workspaceKnowledgeSchema: z.ZodObject<{
34
+ sources: z.ZodArray<z.ZodString>;
35
+ newDocuments: z.ZodString;
36
+ taskVisibility: z.ZodEnum<{
37
+ private: "private";
38
+ shared: "shared";
39
+ }>;
40
+ }, z.core.$strict>;
41
+ export declare const workspaceCodeSchema: z.ZodObject<{
42
+ graph: z.ZodEnum<{
43
+ codegraph: "codegraph";
44
+ none: "none";
45
+ }>;
46
+ symbols: z.ZodEnum<{
47
+ auto: "auto";
48
+ none: "none";
49
+ serena: "serena";
50
+ }>;
51
+ prepare: z.ZodEnum<{
52
+ manual: "manual";
53
+ "on-demand": "on-demand";
54
+ }>;
55
+ }, z.core.$strict>;
56
+ export declare const workspaceValidationSchema: z.ZodObject<{
57
+ id: z.ZodString;
58
+ repositoryId: z.ZodString;
59
+ executable: z.ZodString;
60
+ args: z.ZodArray<z.ZodString>;
61
+ }, z.core.$strict>;
62
+ export declare const workspaceConfigSchema: z.ZodObject<{
63
+ schema: z.ZodLiteral<"pappra/workspace/1">;
64
+ workspaceId: z.ZodUUID;
65
+ name: z.ZodString;
66
+ runtimeVersion: z.ZodString;
67
+ repositories: z.ZodArray<z.ZodObject<{
68
+ id: z.ZodString;
69
+ path: z.ZodString;
70
+ access: z.ZodEnum<{
71
+ read: "read";
72
+ write: "write";
73
+ }>;
74
+ }, z.core.$strict>>;
75
+ knowledge: z.ZodObject<{
76
+ sources: z.ZodArray<z.ZodString>;
77
+ newDocuments: z.ZodString;
78
+ taskVisibility: z.ZodEnum<{
79
+ private: "private";
80
+ shared: "shared";
81
+ }>;
82
+ }, z.core.$strict>;
83
+ code: z.ZodObject<{
84
+ graph: z.ZodEnum<{
85
+ codegraph: "codegraph";
86
+ none: "none";
87
+ }>;
88
+ symbols: z.ZodEnum<{
89
+ auto: "auto";
90
+ none: "none";
91
+ serena: "serena";
92
+ }>;
93
+ prepare: z.ZodEnum<{
94
+ manual: "manual";
95
+ "on-demand": "on-demand";
96
+ }>;
97
+ }, z.core.$strict>;
98
+ validation: z.ZodArray<z.ZodObject<{
99
+ id: z.ZodString;
100
+ repositoryId: z.ZodString;
101
+ executable: z.ZodString;
102
+ args: z.ZodArray<z.ZodString>;
103
+ }, z.core.$strict>>;
104
+ excludes: z.ZodArray<z.ZodString>;
105
+ extensions: z.ZodRecord<z.ZodString, z.ZodUnknown>;
106
+ }, z.core.$strict>;
107
+ export type WorkspaceConfig = z.infer<typeof workspaceConfigSchema>;
108
+ export type WorkspaceRepository = z.infer<typeof workspaceRepositorySchema>;
109
+ export type WorkspaceValidation = z.infer<typeof workspaceValidationSchema>;
110
+ export type RepositoryAccess = z.infer<typeof repositoryAccessSchema>;
111
+ export interface WorkspaceConfigSemanticOptions {
112
+ readonly configPath: string;
113
+ }
114
+ /**
115
+ * Applies rules that cannot be expressed by the structural schema alone.
116
+ * The caller must only use the value when no returned diagnostic is an error.
117
+ */
118
+ export declare function validateWorkspaceConfigSemantics(config: WorkspaceConfig, options: WorkspaceConfigSemanticOptions): readonly Diagnostic[];
119
+ /** Normalizes config-declared excludes for prefix comparisons. */
120
+ export declare function normalizedExcludes(config: WorkspaceConfig): readonly string[];
@@ -0,0 +1,201 @@
1
+ import { z } from 'zod';
2
+ import { createDiagnostic, pointer } from './diagnostics.js';
3
+ import { diagnosticCodes } from './errors.js';
4
+ import { externalRepositoryId, isExternalRepositoryPath, isPortableRelativePath, normalizePortableRelativePath, } from './paths.js';
5
+ export const WORKSPACE_SCHEMA = 'pappra/workspace/1';
6
+ const identifierSchema = z
7
+ .string()
8
+ .min(1)
9
+ .max(64)
10
+ .regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, 'Use a letter or digit followed by letters, digits, dot, underscore or dash.');
11
+ const portablePathSchema = z.string().min(1).max(2048);
12
+ const executableSchema = z
13
+ .string()
14
+ .min(1)
15
+ .max(4096)
16
+ .refine((value) => !value.includes('\0'), 'NUL is not allowed.');
17
+ const argumentSchema = z
18
+ .string()
19
+ .max(4096)
20
+ .refine((value) => !value.includes('\0'), 'NUL is not allowed.');
21
+ export const repositoryAccessSchema = z.enum(['read', 'write']);
22
+ export const codeGraphSchema = z.enum(['codegraph', 'none']);
23
+ export const symbolProviderSchema = z.enum(['auto', 'serena', 'none']);
24
+ export const indexPreparationSchema = z.enum(['on-demand', 'manual']);
25
+ export const taskVisibilitySchema = z.enum(['private', 'shared']);
26
+ export const workspaceRepositorySchema = z.strictObject({
27
+ id: identifierSchema,
28
+ path: portablePathSchema,
29
+ access: repositoryAccessSchema,
30
+ });
31
+ export const workspaceKnowledgeSchema = z.strictObject({
32
+ sources: z.array(portablePathSchema).max(128),
33
+ newDocuments: portablePathSchema,
34
+ taskVisibility: taskVisibilitySchema,
35
+ });
36
+ export const workspaceCodeSchema = z.strictObject({
37
+ graph: codeGraphSchema,
38
+ symbols: symbolProviderSchema,
39
+ prepare: indexPreparationSchema,
40
+ });
41
+ export const workspaceValidationSchema = z.strictObject({
42
+ id: identifierSchema,
43
+ repositoryId: identifierSchema,
44
+ executable: executableSchema,
45
+ args: z.array(argumentSchema).max(256),
46
+ });
47
+ export const workspaceConfigSchema = z.strictObject({
48
+ schema: z.literal(WORKSPACE_SCHEMA),
49
+ workspaceId: z.uuid(),
50
+ name: z.string().min(1).max(200),
51
+ runtimeVersion: z.string().min(1).max(128),
52
+ repositories: z.array(workspaceRepositorySchema).max(128),
53
+ knowledge: workspaceKnowledgeSchema,
54
+ code: workspaceCodeSchema,
55
+ validation: z.array(workspaceValidationSchema).max(128),
56
+ excludes: z.array(portablePathSchema).max(128),
57
+ extensions: z.record(z.string().min(1).max(128), z.unknown()),
58
+ });
59
+ function pathDiagnostic(value, source, fieldPointer, label) {
60
+ const hint = 'Use a workspace-relative path such as "docs" or "repos/app".';
61
+ if (value.includes('\0') || value.includes('\\')) {
62
+ return createDiagnostic({
63
+ code: diagnosticCodes.configPathInvalid,
64
+ severity: 'error',
65
+ message: `${label} must use forward slashes and must not contain NUL or backslashes.`,
66
+ source,
67
+ pointer: fieldPointer,
68
+ hint,
69
+ });
70
+ }
71
+ if (value.startsWith('/') ||
72
+ /^[A-Za-z]:/.test(value) ||
73
+ value.startsWith('\\\\')) {
74
+ return createDiagnostic({
75
+ code: diagnosticCodes.configPathAbsolute,
76
+ severity: 'error',
77
+ message: `${label} must be relative to the workspace root, not absolute.`,
78
+ source,
79
+ pointer: fieldPointer,
80
+ hint,
81
+ });
82
+ }
83
+ if (!isPortableRelativePath(value)) {
84
+ return createDiagnostic({
85
+ code: diagnosticCodes.configPathEscapesRoot,
86
+ severity: 'error',
87
+ message: `${label} escapes the workspace root.`,
88
+ source,
89
+ pointer: fieldPointer,
90
+ hint,
91
+ });
92
+ }
93
+ return null;
94
+ }
95
+ /**
96
+ * Applies rules that cannot be expressed by the structural schema alone.
97
+ * The caller must only use the value when no returned diagnostic is an error.
98
+ */
99
+ export function validateWorkspaceConfigSemantics(config, options) {
100
+ const { configPath } = options;
101
+ const diagnostics = [];
102
+ const repositoryIds = new Map();
103
+ const validationIds = new Map();
104
+ config.repositories.forEach((repository, index) => {
105
+ const first = repositoryIds.get(repository.id);
106
+ if (first !== undefined) {
107
+ diagnostics.push(createDiagnostic({
108
+ code: diagnosticCodes.configDuplicateRepositoryId,
109
+ severity: 'error',
110
+ message: `Repository id "${repository.id}" is already used by repositories[${first}].`,
111
+ source: configPath,
112
+ pointer: pointer('repositories', index, 'id'),
113
+ hint: 'Give every repository a unique id within the workspace.',
114
+ }));
115
+ }
116
+ else {
117
+ repositoryIds.set(repository.id, index);
118
+ }
119
+ if (isExternalRepositoryPath(repository.path)) {
120
+ if (externalRepositoryId(repository.path) === null) {
121
+ diagnostics.push(createDiagnostic({
122
+ code: diagnosticCodes.configPathInvalid,
123
+ severity: 'error',
124
+ message: `Repository path "${repository.path}" is not a valid external:<id> reference.`,
125
+ source: configPath,
126
+ pointer: pointer('repositories', index, 'path'),
127
+ hint: 'Use external:<logical-id> and declare the absolute location in .pappra/local/machine.json.',
128
+ }));
129
+ }
130
+ }
131
+ else {
132
+ const pathIssue = pathDiagnostic(repository.path, configPath, pointer('repositories', index, 'path'), `Repository "${repository.id}" path`);
133
+ if (pathIssue !== null)
134
+ diagnostics.push(pathIssue);
135
+ }
136
+ });
137
+ config.validation.forEach((validation, index) => {
138
+ const first = validationIds.get(validation.id);
139
+ if (first !== undefined) {
140
+ diagnostics.push(createDiagnostic({
141
+ code: diagnosticCodes.configDuplicateValidationId,
142
+ severity: 'error',
143
+ message: `Validation id "${validation.id}" is already used by validation[${first}].`,
144
+ source: configPath,
145
+ pointer: pointer('validation', index, 'id'),
146
+ hint: 'Give every validation command a unique id.',
147
+ }));
148
+ }
149
+ else {
150
+ validationIds.set(validation.id, index);
151
+ }
152
+ if (!repositoryIds.has(validation.repositoryId)) {
153
+ diagnostics.push(createDiagnostic({
154
+ code: diagnosticCodes.configUnknownRepository,
155
+ severity: 'error',
156
+ message: `Validation "${validation.id}" refers to unknown repository "${validation.repositoryId}".`,
157
+ source: configPath,
158
+ pointer: pointer('validation', index, 'repositoryId'),
159
+ hint: 'Declare the repository first or correct the repositoryId.',
160
+ }));
161
+ }
162
+ });
163
+ config.knowledge.sources.forEach((source, index) => {
164
+ const pathIssue = pathDiagnostic(source, configPath, pointer('knowledge', 'sources', index), 'Knowledge source');
165
+ if (pathIssue !== null)
166
+ diagnostics.push(pathIssue);
167
+ if (isExternalRepositoryPath(source)) {
168
+ diagnostics.push(createDiagnostic({
169
+ code: diagnosticCodes.configPathInvalid,
170
+ severity: 'error',
171
+ message: 'Knowledge sources must stay inside the workspace; external: references are only valid for repositories.',
172
+ source: configPath,
173
+ pointer: pointer('knowledge', 'sources', index),
174
+ hint: 'Copy no external knowledge; declare an external repository and read its documentation in place.',
175
+ }));
176
+ }
177
+ });
178
+ const newDocumentsIssue = pathDiagnostic(config.knowledge.newDocuments, configPath, pointer('knowledge', 'newDocuments'), 'New document directory');
179
+ if (newDocumentsIssue !== null)
180
+ diagnostics.push(newDocumentsIssue);
181
+ config.excludes.forEach((exclude, index) => {
182
+ const pathIssue = pathDiagnostic(exclude, configPath, pointer('excludes', index), 'Exclude entry');
183
+ if (pathIssue !== null)
184
+ diagnostics.push(pathIssue);
185
+ if (/[*?[\]{}]/.test(exclude)) {
186
+ diagnostics.push(createDiagnostic({
187
+ code: diagnosticCodes.configGlobNotSupported,
188
+ severity: 'error',
189
+ message: `Exclude entry "${exclude}" contains glob metacharacters.`,
190
+ source: configPath,
191
+ pointer: pointer('excludes', index),
192
+ hint: 'Use literal workspace-relative file or directory prefixes; glob expansion is not part of schema version 1.',
193
+ }));
194
+ }
195
+ });
196
+ return diagnostics;
197
+ }
198
+ /** Normalizes config-declared excludes for prefix comparisons. */
199
+ export function normalizedExcludes(config) {
200
+ return config.excludes.map((entry) => normalizePortableRelativePath(entry));
201
+ }
@@ -0,0 +1,36 @@
1
+ import type { z } from 'zod';
2
+ import type { Diagnostic, DiagnosticSeverity } from '../schemas/diagnostics.js';
3
+ import { type Outcome } from '../schemas/outcome.js';
4
+ export interface ConfigFileInspection<T> {
5
+ /** The path the caller asked to inspect (not silently replaced by a symlink target). */
6
+ readonly path: string;
7
+ readonly exists: boolean;
8
+ readonly digest: string | null;
9
+ readonly size: number | null;
10
+ readonly value: T | null;
11
+ readonly diagnostics: readonly Diagnostic[];
12
+ }
13
+ export interface ConfigFileMissingPolicy {
14
+ readonly severity: DiagnosticSeverity;
15
+ readonly message: string;
16
+ readonly hint: string;
17
+ }
18
+ export interface InspectConfigFileOptions<T> {
19
+ readonly path: string;
20
+ readonly schemaName: string;
21
+ readonly schema: z.ZodType<T>;
22
+ readonly missing: ConfigFileMissingPolicy;
23
+ /** When set, the real path of the file must stay inside this real directory. */
24
+ readonly containmentRoot?: string;
25
+ readonly maxBytes?: number;
26
+ readonly semantic?: (value: T) => readonly Diagnostic[];
27
+ }
28
+ /**
29
+ * Reads, decodes, validates, and semantically checks one strict JSON
30
+ * configuration file. Missing configuration is reported instead of defaulted.
31
+ */
32
+ export declare function inspectConfigFile<T>(options: InspectConfigFileOptions<T>): Promise<ConfigFileInspection<T>>;
33
+ /** Converts an inspection into a convenience result for callers that fail closed. */
34
+ export declare function requireConfig<T>(inspection: ConfigFileInspection<T>): Outcome<T>;
35
+ /** Joins a workspace-relative configuration path without normalizing above root. */
36
+ export declare function workspaceConfigPath(root: string): string;