peaks-loop 4.0.23 → 4.0.24

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 (57) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/cli/commands/_register.js +2 -1
  3. package/dist/cli/commands/best-practice-scan-command.d.ts +36 -0
  4. package/dist/cli/commands/best-practice-scan-command.js +118 -0
  5. package/dist/cli/commands/request-commands.js +21 -9
  6. package/dist/services/best-practice/language-detector.d.ts +7 -0
  7. package/dist/services/best-practice/language-detector.js +96 -0
  8. package/dist/services/best-practice/output-formatter.d.ts +41 -0
  9. package/dist/services/best-practice/output-formatter.js +116 -0
  10. package/dist/services/best-practice/scan-orchestrator.d.ts +39 -0
  11. package/dist/services/best-practice/scan-orchestrator.js +85 -0
  12. package/dist/services/context/collector.d.ts +5 -12
  13. package/dist/services/context/context-builder.d.ts +12 -20
  14. package/dist/services/context/context-builder.js +1 -1
  15. package/dist/services/context/context-schema.d.ts +51 -364
  16. package/dist/services/context/context-schema.js +3 -3
  17. package/dist/services/crystallization/crystallization-service.d.ts +2 -12
  18. package/dist/services/crystallization/crystallization-service.js +3 -9
  19. package/dist/services/crystallization/crystallization-types.d.ts +45 -153
  20. package/dist/services/crystallization/crystallization-types.js +17 -11
  21. package/dist/services/crystallization/evidence-brief-builder.d.ts +12 -112
  22. package/dist/services/evolution/evolution-types.d.ts +83 -497
  23. package/dist/services/evolution/evolution-types.js +6 -10
  24. package/dist/services/fixture/fixture-sanitize-service.d.ts +10 -36
  25. package/dist/services/fixture/fixture-sanitize-service.js +1 -1
  26. package/dist/services/job/job-progress-store.d.ts +1 -17
  27. package/dist/services/job/job-progress-store.js +2 -2
  28. package/dist/services/job/job-types.d.ts +63 -370
  29. package/dist/services/job/job-types.js +46 -11
  30. package/dist/services/loop/loop-bee-relation-types.d.ts +20 -32
  31. package/dist/services/loop/loop-bee-relation-types.js +2 -2
  32. package/dist/services/loop/loop-release-types.d.ts +31 -110
  33. package/dist/services/migrate-skill-name/schema.d.ts +3 -19
  34. package/dist/services/migrate-skill-name/schema.js +4 -4
  35. package/dist/services/observability/observability-service.d.ts +21 -19
  36. package/dist/services/observability/observability-service.js +1 -1
  37. package/dist/services/prd/best-practice-auto-trigger.d.ts +61 -0
  38. package/dist/services/prd/best-practice-auto-trigger.js +126 -0
  39. package/dist/services/rd/types.d.ts +25 -194
  40. package/dist/services/sediment/json-schema.d.ts +39 -224
  41. package/dist/services/sediment/json-schema.js +2 -2
  42. package/dist/services/session/binding-store.d.ts +5 -47
  43. package/dist/services/session/binding-store.js +1 -1
  44. package/dist/services/share/bundle-types.d.ts +12 -180
  45. package/dist/services/share/bundle-types.js +15 -9
  46. package/dist/services/share/run-state-contract.d.ts +8 -25
  47. package/dist/services/skill/skill-search-service.d.ts +26 -46
  48. package/dist/services/skill/skill-search-service.js +2 -2
  49. package/dist/services/skills/hooks-settings-service.d.ts +9 -0
  50. package/dist/services/skills/hooks-settings-service.js +31 -1
  51. package/dist/services/skills/skill-presence-service.d.ts +24 -0
  52. package/dist/services/skills/skill-presence-service.js +33 -0
  53. package/package.json +6 -6
  54. package/schemas/context.schema.json +17 -8
  55. package/skills/bee/peaks-prd/SKILL.md +17 -0
  56. package/skills/peaks-code/SKILL.md +28 -1
  57. package/skills/peaks-code/references/skill-presence-and-title.md +3 -3
@@ -8,12 +8,32 @@ export const SliceStateSchema = z
8
8
  commitSha: z.string().optional(), // required when status=done
9
9
  finishedAt: z.string().datetime().optional(),
10
10
  failureReason: z.string().optional(), // required when status=failed
11
- repairCycles: z.number().int().nonnegative().default(0),
11
+ repairCycles: z.number().int().gte(0).default(0),
12
12
  blockedReason: z.string().optional(), // required when status=blocked
13
13
  })
14
- .refine((v) => v.status !== 'done' || !!(v.commitSha && v.commitSha.length >= 7), { message: 'commitSha required (≥7 hex) when status=done', path: ['commitSha'] })
15
- .refine((v) => v.status !== 'failed' || !!(v.failureReason && v.failureReason.length >= 1), { message: 'failureReason required when status=failed', path: ['failureReason'] })
16
- .refine((v) => v.status !== 'blocked' || !!(v.blockedReason && v.blockedReason.length >= 1), { message: 'blockedReason required when status=blocked', path: ['blockedReason'] });
14
+ .superRefine((v, ctx) => {
15
+ if (v.status === 'done' && !(v.commitSha && v.commitSha.length >= 7)) {
16
+ ctx.addIssue({
17
+ code: 'custom',
18
+ path: ['commitSha'],
19
+ message: 'commitSha required (≥7 hex) when status=done',
20
+ });
21
+ }
22
+ if (v.status === 'failed' && !(v.failureReason && v.failureReason.length >= 1)) {
23
+ ctx.addIssue({
24
+ code: 'custom',
25
+ path: ['failureReason'],
26
+ message: 'failureReason required when status=failed',
27
+ });
28
+ }
29
+ if (v.status === 'blocked' && !(v.blockedReason && v.blockedReason.length >= 1)) {
30
+ ctx.addIssue({
31
+ code: 'custom',
32
+ path: ['blockedReason'],
33
+ message: 'blockedReason required when status=blocked',
34
+ });
35
+ }
36
+ });
17
37
  export const JobStateSchema = z.object({
18
38
  jobId: z.string(),
19
39
  sessionId: z.string(),
@@ -22,8 +42,8 @@ export const JobStateSchema = z.object({
22
42
  parallelismHint: z.enum(['serial', 'llm-decides']).default('llm-decides'),
23
43
  exitPolicy: z.enum(['strict', 'best-effort']).default('strict'),
24
44
  mainLoopStrategy: z.enum(['single', 'rotating']).default('rotating'),
25
- rotateEvery: z.number().int().positive().default(3),
26
- mainSessionCycle: z.number().int().nonnegative().default(0),
45
+ rotateEvery: z.number().int().gt(0).default(3),
46
+ mainSessionCycle: z.number().int().gte(0).default(0),
27
47
  mainLoopOverride: z
28
48
  .object({
29
49
  from: z.literal('rotating'),
@@ -37,8 +57,8 @@ export const JobStateSchema = z.object({
37
57
  export const ResourceSnapshotSchema = z.object({
38
58
  capturedAt: z.string().datetime(),
39
59
  cpuPercent: z.number().min(0).max(100),
40
- memMb: z.number().nonnegative(),
41
- diskMb: z.number().nonnegative(),
60
+ memMb: z.number().gte(0),
61
+ diskMb: z.number().gte(0),
42
62
  contextRatio: z.number().min(0).max(1),
43
63
  });
44
64
  export const JobStatusSummarySchema = z.object({
@@ -62,7 +82,7 @@ export const JobInitInputSchema = z.object({
62
82
  parallelismHint: z.enum(['serial', 'llm-decides']).default('llm-decides'),
63
83
  exitPolicy: z.enum(['strict', 'best-effort']).default('strict'),
64
84
  mainLoopStrategy: z.enum(['single', 'rotating']).default('rotating'),
65
- rotateEvery: z.number().int().positive().default(3),
85
+ rotateEvery: z.number().int().gt(0).default(3),
66
86
  project: z.string(),
67
87
  json: z.boolean().default(true),
68
88
  });
@@ -76,8 +96,23 @@ export const JobCheckpointInputSchema = z
76
96
  project: z.string(),
77
97
  json: z.boolean().default(true),
78
98
  })
79
- .refine((v) => v.state !== 'done' || (v.commitSha && v.commitSha.length >= 7), { message: 'commitSha required (≥7 hex) when state=done', path: ['commitSha'] })
80
- .refine((v) => v.state !== 'failed' && v.state !== 'skipped' ? true : !!(v.reason && v.reason.length >= 3), { message: 'reason required (≥3 chars) when state=failed|skipped', path: ['reason'] });
99
+ .superRefine((v, ctx) => {
100
+ if (v.state === 'done' && !(v.commitSha && v.commitSha.length >= 7)) {
101
+ ctx.addIssue({
102
+ code: 'custom',
103
+ path: ['commitSha'],
104
+ message: 'commitSha required (≥7 hex) when state=done',
105
+ });
106
+ }
107
+ if ((v.state === 'failed' || v.state === 'skipped') &&
108
+ !(v.reason && v.reason.length >= 3)) {
109
+ ctx.addIssue({
110
+ code: 'custom',
111
+ path: ['reason'],
112
+ message: 'reason required (≥3 chars) when state=failed|skipped',
113
+ });
114
+ }
115
+ });
81
116
  export const JobBlockInputSchema = z.object({
82
117
  jobId: z.string(),
83
118
  sliceId: z.string(),
@@ -27,7 +27,12 @@ import { z } from "zod";
27
27
  * - `created_at` is server-stamped from the system clock; the input
28
28
  * schema does not let clients supply it (would be a backdate hole).
29
29
  */
30
- export declare const LoopBeeRelationRoleSchema: z.ZodEnum<["main", "supporting", "candidate", "retired"]>;
30
+ export declare const LoopBeeRelationRoleSchema: z.ZodEnum<{
31
+ main: "main";
32
+ candidate: "candidate";
33
+ retired: "retired";
34
+ supporting: "supporting";
35
+ }>;
31
36
  export type LoopBeeRelationRole = z.infer<typeof LoopBeeRelationRoleSchema>;
32
37
  export declare const LOOP_BEE_RELATION_ROLES: readonly LoopBeeRelationRole[];
33
38
  /**
@@ -44,19 +49,14 @@ export declare const LOOP_BEE_RELATION_ROLES: readonly LoopBeeRelationRole[];
44
49
  export declare const LoopBeeRelationInputSchema: z.ZodObject<{
45
50
  loop_release_id: z.ZodString;
46
51
  bee_release_id: z.ZodNumber;
47
- role: z.ZodEnum<["main", "supporting", "candidate", "retired"]>;
52
+ role: z.ZodEnum<{
53
+ main: "main";
54
+ candidate: "candidate";
55
+ retired: "retired";
56
+ supporting: "supporting";
57
+ }>;
48
58
  reason: z.ZodString;
49
- }, "strip", z.ZodTypeAny, {
50
- bee_release_id: number;
51
- loop_release_id: string;
52
- role: "main" | "candidate" | "retired" | "supporting";
53
- reason: string;
54
- }, {
55
- bee_release_id: number;
56
- loop_release_id: string;
57
- role: "main" | "candidate" | "retired" | "supporting";
58
- reason: string;
59
- }>;
59
+ }, z.core.$strip>;
60
60
  export type LoopBeeRelationInput = z.input<typeof LoopBeeRelationInputSchema>;
61
61
  /**
62
62
  * Full Zod schema for a LoopBeeRelation row as persisted. Differs from
@@ -72,29 +72,17 @@ export type LoopBeeRelationInput = z.input<typeof LoopBeeRelationInputSchema>;
72
72
  export declare const LoopBeeRelationSchema: z.ZodObject<{
73
73
  loop_release_id: z.ZodString;
74
74
  bee_release_id: z.ZodNumber;
75
- role: z.ZodEnum<["main", "supporting", "candidate", "retired"]>;
75
+ role: z.ZodEnum<{
76
+ main: "main";
77
+ candidate: "candidate";
78
+ retired: "retired";
79
+ supporting: "supporting";
80
+ }>;
76
81
  reason: z.ZodString;
77
- } & {
78
82
  id: z.ZodNumber;
79
83
  schema_version: z.ZodDefault<z.ZodLiteral<"peaks.loop-bee-relation/1">>;
80
84
  created_at: z.ZodString;
81
- }, "strip", z.ZodTypeAny, {
82
- schema_version: "peaks.loop-bee-relation/1";
83
- id: number;
84
- created_at: string;
85
- bee_release_id: number;
86
- loop_release_id: string;
87
- role: "main" | "candidate" | "retired" | "supporting";
88
- reason: string;
89
- }, {
90
- id: number;
91
- created_at: string;
92
- bee_release_id: number;
93
- loop_release_id: string;
94
- role: "main" | "candidate" | "retired" | "supporting";
95
- reason: string;
96
- schema_version?: "peaks.loop-bee-relation/1" | undefined;
97
- }>;
85
+ }, z.core.$strip>;
98
86
  export type LoopBeeRelation = z.infer<typeof LoopBeeRelationSchema>;
99
87
  /**
100
88
  * Convenience: strict-parse an unknown payload into a LoopBeeRelation
@@ -61,7 +61,7 @@ export const LoopBeeRelationInputSchema = z.object({
61
61
  bee_release_id: z
62
62
  .number()
63
63
  .int()
64
- .positive()
64
+ .gt(0)
65
65
  .max(2 ** 31 - 1, "bee_release_id out of 32-bit range"),
66
66
  role: LoopBeeRelationRoleSchema,
67
67
  reason: z
@@ -82,7 +82,7 @@ export const LoopBeeRelationInputSchema = z.object({
82
82
  * read paths.
83
83
  */
84
84
  export const LoopBeeRelationSchema = LoopBeeRelationInputSchema.extend({
85
- id: z.number().int().positive(),
85
+ id: z.number().int().gt(0),
86
86
  schema_version: z
87
87
  .literal("peaks.loop-bee-relation/1")
88
88
  .default("peaks.loop-bee-relation/1"),
@@ -40,7 +40,11 @@ import { z } from "zod";
40
40
  * `"peaks.bundle/1"` per the spec §7A.2 contract — no other
41
41
  * value is accepted in this schema version.
42
42
  */
43
- export declare const LoopReleaseLifecycleStatusSchema: z.ZodEnum<["candidate", "stable", "retired"]>;
43
+ export declare const LoopReleaseLifecycleStatusSchema: z.ZodEnum<{
44
+ candidate: "candidate";
45
+ stable: "stable";
46
+ retired: "retired";
47
+ }>;
44
48
  export type LoopReleaseLifecycleStatus = z.infer<typeof LoopReleaseLifecycleStatusSchema>;
45
49
  /**
46
50
  * The M3 share / desktop extension fields, listed for code-search /
@@ -65,20 +69,10 @@ export declare const LOOP_RELEASE_M3_FIELDS: readonly ["shareable", "share_exclu
65
69
  */
66
70
  export declare const LoopReleaseM3ExtensionSchema: z.ZodObject<{
67
71
  shareable: z.ZodDefault<z.ZodBoolean>;
68
- share_excluded_paths: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
72
+ share_excluded_paths: z.ZodDefault<z.ZodArray<z.ZodString>>;
69
73
  desktop_visible: z.ZodDefault<z.ZodBoolean>;
70
74
  export_bundle_format: z.ZodDefault<z.ZodLiteral<"peaks.bundle/1">>;
71
- }, "strip", z.ZodTypeAny, {
72
- shareable: boolean;
73
- share_excluded_paths: string[];
74
- desktop_visible: boolean;
75
- export_bundle_format: "peaks.bundle/1";
76
- }, {
77
- shareable?: boolean | undefined;
78
- share_excluded_paths?: string[] | undefined;
79
- desktop_visible?: boolean | undefined;
80
- export_bundle_format?: "peaks.bundle/1" | undefined;
81
- }>;
75
+ }, z.core.$strip>;
82
76
  /** Convenience type for the M3 share / desktop subset. */
83
77
  export type LoopReleaseM3Extension = z.infer<typeof LoopReleaseM3ExtensionSchema>;
84
78
  /**
@@ -92,60 +86,25 @@ export declare const LoopReleaseInputSchema: z.ZodObject<{
92
86
  name: z.ZodString;
93
87
  scenario: z.ZodString;
94
88
  trigger_policy: z.ZodString;
95
- success_criteria: z.ZodArray<z.ZodString, "many">;
89
+ success_criteria: z.ZodArray<z.ZodString>;
96
90
  interaction_policy: z.ZodString;
97
91
  feedback_policy: z.ZodString;
98
92
  evolution_policy: z.ZodString;
99
- evaluator_policy: z.ZodArray<z.ZodString, "many">;
100
- linked_bees: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
101
- run_history: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
102
- crystallization_evidence: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
103
- lifecycle_status: z.ZodEnum<["candidate", "stable", "retired"]>;
93
+ evaluator_policy: z.ZodArray<z.ZodString>;
94
+ linked_bees: z.ZodDefault<z.ZodArray<z.ZodString>>;
95
+ run_history: z.ZodDefault<z.ZodArray<z.ZodString>>;
96
+ crystallization_evidence: z.ZodDefault<z.ZodArray<z.ZodString>>;
97
+ lifecycle_status: z.ZodEnum<{
98
+ candidate: "candidate";
99
+ stable: "stable";
100
+ retired: "retired";
101
+ }>;
104
102
  version: z.ZodString;
105
- } & {
106
103
  shareable: z.ZodDefault<z.ZodBoolean>;
107
- share_excluded_paths: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
104
+ share_excluded_paths: z.ZodDefault<z.ZodArray<z.ZodString>>;
108
105
  desktop_visible: z.ZodDefault<z.ZodBoolean>;
109
106
  export_bundle_format: z.ZodDefault<z.ZodLiteral<"peaks.bundle/1">>;
110
- }, "strip", z.ZodTypeAny, {
111
- version: string;
112
- name: string;
113
- lifecycle_status: "candidate" | "stable" | "retired";
114
- id: string;
115
- shareable: boolean;
116
- share_excluded_paths: string[];
117
- desktop_visible: boolean;
118
- export_bundle_format: "peaks.bundle/1";
119
- scenario: string;
120
- trigger_policy: string;
121
- success_criteria: string[];
122
- interaction_policy: string;
123
- feedback_policy: string;
124
- evolution_policy: string;
125
- evaluator_policy: string[];
126
- linked_bees: string[];
127
- run_history: string[];
128
- crystallization_evidence: string[];
129
- }, {
130
- version: string;
131
- name: string;
132
- lifecycle_status: "candidate" | "stable" | "retired";
133
- id: string;
134
- scenario: string;
135
- trigger_policy: string;
136
- success_criteria: string[];
137
- interaction_policy: string;
138
- feedback_policy: string;
139
- evolution_policy: string;
140
- evaluator_policy: string[];
141
- shareable?: boolean | undefined;
142
- share_excluded_paths?: string[] | undefined;
143
- desktop_visible?: boolean | undefined;
144
- export_bundle_format?: "peaks.bundle/1" | undefined;
145
- linked_bees?: string[] | undefined;
146
- run_history?: string[] | undefined;
147
- crystallization_evidence?: string[] | undefined;
148
- }>;
107
+ }, z.core.$strip>;
149
108
  /**
150
109
  * `LoopReleaseInput` is the *input* shape clients supply: defaulted
151
110
  * fields (linked_bees / run_history / crystallization_evidence) are
@@ -170,64 +129,26 @@ export declare const LoopReleaseSchema: z.ZodObject<{
170
129
  name: z.ZodString;
171
130
  scenario: z.ZodString;
172
131
  trigger_policy: z.ZodString;
173
- success_criteria: z.ZodArray<z.ZodString, "many">;
132
+ success_criteria: z.ZodArray<z.ZodString>;
174
133
  interaction_policy: z.ZodString;
175
134
  feedback_policy: z.ZodString;
176
135
  evolution_policy: z.ZodString;
177
- evaluator_policy: z.ZodArray<z.ZodString, "many">;
178
- linked_bees: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
179
- run_history: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
180
- crystallization_evidence: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
181
- lifecycle_status: z.ZodEnum<["candidate", "stable", "retired"]>;
136
+ evaluator_policy: z.ZodArray<z.ZodString>;
137
+ linked_bees: z.ZodDefault<z.ZodArray<z.ZodString>>;
138
+ run_history: z.ZodDefault<z.ZodArray<z.ZodString>>;
139
+ crystallization_evidence: z.ZodDefault<z.ZodArray<z.ZodString>>;
140
+ lifecycle_status: z.ZodEnum<{
141
+ candidate: "candidate";
142
+ stable: "stable";
143
+ retired: "retired";
144
+ }>;
182
145
  version: z.ZodString;
183
- } & {
184
146
  shareable: z.ZodDefault<z.ZodBoolean>;
185
- share_excluded_paths: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
147
+ share_excluded_paths: z.ZodDefault<z.ZodArray<z.ZodString>>;
186
148
  desktop_visible: z.ZodDefault<z.ZodBoolean>;
187
149
  export_bundle_format: z.ZodDefault<z.ZodLiteral<"peaks.bundle/1">>;
188
- } & {
189
150
  schema_version: z.ZodDefault<z.ZodLiteral<"peaks.loop/1">>;
190
- }, "strip", z.ZodTypeAny, {
191
- schema_version: "peaks.loop/1";
192
- version: string;
193
- name: string;
194
- lifecycle_status: "candidate" | "stable" | "retired";
195
- id: string;
196
- shareable: boolean;
197
- share_excluded_paths: string[];
198
- desktop_visible: boolean;
199
- export_bundle_format: "peaks.bundle/1";
200
- scenario: string;
201
- trigger_policy: string;
202
- success_criteria: string[];
203
- interaction_policy: string;
204
- feedback_policy: string;
205
- evolution_policy: string;
206
- evaluator_policy: string[];
207
- linked_bees: string[];
208
- run_history: string[];
209
- crystallization_evidence: string[];
210
- }, {
211
- version: string;
212
- name: string;
213
- lifecycle_status: "candidate" | "stable" | "retired";
214
- id: string;
215
- scenario: string;
216
- trigger_policy: string;
217
- success_criteria: string[];
218
- interaction_policy: string;
219
- feedback_policy: string;
220
- evolution_policy: string;
221
- evaluator_policy: string[];
222
- schema_version?: "peaks.loop/1" | undefined;
223
- shareable?: boolean | undefined;
224
- share_excluded_paths?: string[] | undefined;
225
- desktop_visible?: boolean | undefined;
226
- export_bundle_format?: "peaks.bundle/1" | undefined;
227
- linked_bees?: string[] | undefined;
228
- run_history?: string[] | undefined;
229
- crystallization_evidence?: string[] | undefined;
230
- }>;
151
+ }, z.core.$strip>;
231
152
  export type LoopRelease = z.infer<typeof LoopReleaseSchema>;
232
153
  /**
233
154
  * Convenience: strict-parse an unknown payload into a LoopRelease row.
@@ -20,23 +20,7 @@ export declare const MigrateResultSchema: z.ZodObject<{
20
20
  modifiedFiles: z.ZodNumber;
21
21
  keyValueReplacements: z.ZodNumber;
22
22
  stringReplacements: z.ZodNumber;
23
- skipped: z.ZodArray<z.ZodString, "many">;
24
- errors: z.ZodArray<z.ZodString, "many">;
25
- }, "strip", z.ZodTypeAny, {
26
- skipped: string[];
27
- ok: boolean;
28
- scannedFiles: number;
29
- modifiedFiles: number;
30
- keyValueReplacements: number;
31
- stringReplacements: number;
32
- errors: string[];
33
- }, {
34
- skipped: string[];
35
- ok: boolean;
36
- scannedFiles: number;
37
- modifiedFiles: number;
38
- keyValueReplacements: number;
39
- stringReplacements: number;
40
- errors: string[];
41
- }>;
23
+ skipped: z.ZodArray<z.ZodString>;
24
+ errors: z.ZodArray<z.ZodString>;
25
+ }, z.core.$strip>;
42
26
  export type MigrateResult = z.infer<typeof MigrateResultSchema>;
@@ -16,10 +16,10 @@ import { z } from 'zod';
16
16
  */
17
17
  export const MigrateResultSchema = z.object({
18
18
  ok: z.boolean(),
19
- scannedFiles: z.number().int().nonnegative(),
20
- modifiedFiles: z.number().int().nonnegative(),
21
- keyValueReplacements: z.number().int().nonnegative(),
22
- stringReplacements: z.number().int().nonnegative(),
19
+ scannedFiles: z.number().int().gte(0),
20
+ modifiedFiles: z.number().int().gte(0),
21
+ keyValueReplacements: z.number().int().gte(0),
22
+ stringReplacements: z.number().int().gte(0),
23
23
  skipped: z.array(z.string()),
24
24
  errors: z.array(z.string()),
25
25
  });
@@ -28,27 +28,29 @@ export declare const ObservabilityEventSchema: z.ZodObject<{
28
28
  schemaVersion: z.ZodLiteral<1>;
29
29
  ts: z.ZodString;
30
30
  sessionId: z.ZodString;
31
- category: z.ZodEnum<["slice-transition", "dispatch", "checkpoint", "mode-gate", "context-trigger", "post-compact", "cycle", "token-usage", "monotonic-trigger", "lease"]>;
31
+ category: z.ZodEnum<{
32
+ dispatch: "dispatch";
33
+ "slice-transition": "slice-transition";
34
+ checkpoint: "checkpoint";
35
+ "mode-gate": "mode-gate";
36
+ "context-trigger": "context-trigger";
37
+ "post-compact": "post-compact";
38
+ cycle: "cycle";
39
+ "token-usage": "token-usage";
40
+ "monotonic-trigger": "monotonic-trigger";
41
+ lease: "lease";
42
+ }>;
32
43
  sliceRid: z.ZodOptional<z.ZodString>;
33
- role: z.ZodOptional<z.ZodEnum<["rd", "qa", "code-reviewer", "karpathy-reviewer", "peaks-security-audit", "peaks-perf-audit"]>>;
44
+ role: z.ZodOptional<z.ZodEnum<{
45
+ qa: "qa";
46
+ rd: "rd";
47
+ "code-reviewer": "code-reviewer";
48
+ "karpathy-reviewer": "karpathy-reviewer";
49
+ "peaks-security-audit": "peaks-security-audit";
50
+ "peaks-perf-audit": "peaks-perf-audit";
51
+ }>>;
34
52
  detail: z.ZodRecord<z.ZodString, z.ZodUnknown>;
35
- }, "strip", z.ZodTypeAny, {
36
- schemaVersion: 1;
37
- sessionId: string;
38
- detail: Record<string, unknown>;
39
- ts: string;
40
- category: "dispatch" | "slice-transition" | "checkpoint" | "mode-gate" | "context-trigger" | "post-compact" | "cycle" | "token-usage" | "monotonic-trigger" | "lease";
41
- role?: "qa" | "rd" | "code-reviewer" | "karpathy-reviewer" | "peaks-security-audit" | "peaks-perf-audit" | undefined;
42
- sliceRid?: string | undefined;
43
- }, {
44
- schemaVersion: 1;
45
- sessionId: string;
46
- detail: Record<string, unknown>;
47
- ts: string;
48
- category: "dispatch" | "slice-transition" | "checkpoint" | "mode-gate" | "context-trigger" | "post-compact" | "cycle" | "token-usage" | "monotonic-trigger" | "lease";
49
- role?: "qa" | "rd" | "code-reviewer" | "karpathy-reviewer" | "peaks-security-audit" | "peaks-perf-audit" | undefined;
50
- sliceRid?: string | undefined;
51
- }>;
53
+ }, z.core.$strip>;
52
54
  export type ObservabilityEvent = z.infer<typeof ObservabilityEventSchema>;
53
55
  export type EmitOptions = {
54
56
  /** Absolute path to the project root (where `.peaks/_runtime/` lives). */
@@ -61,7 +61,7 @@ export const ObservabilityEventSchema = z.object({
61
61
  category: z.enum(OBSERVABILITY_CATEGORIES),
62
62
  sliceRid: z.string().min(1).optional(),
63
63
  role: z.enum(OBSERVABILITY_SUBAGENT_ROLES).optional(),
64
- detail: z.record(z.unknown())
64
+ detail: z.record(z.string(), z.unknown())
65
65
  });
66
66
  /**
67
67
  * Append a single observability event to the session's JSONL metrics
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Slice 2026-08-12 best-practice-scan — auto-trigger post-step (Slice F).
3
+ *
4
+ * When peaks-prd's request artifact transitions to `handed-off`, this helper
5
+ * extracts the `businessGoal` (first non-empty bullet under `## Goals` of the
6
+ * PRD body) and fires `peaks best-practice-scan --project <root> --intent "<goal>"`
7
+ * as a fire-and-forget post-step. The PRD → handed-off transition is the
8
+ * canonical moment when "businessGoal transitions to complete" (the prd state
9
+ * machine's terminal state).
10
+ *
11
+ * Failure modes (all non-fatal — the prd → handed-off transition MUST NOT
12
+ * block on BPS issues):
13
+ * - Empty businessGoal → status: 'skipped-empty-goal' (no spawn)
14
+ * - Artifact missing → status: 'skipped-artifact-missing'
15
+ * - Peaks binary missing → status: 'skipped-artifact-missing'
16
+ * - Spawn 'error' event → status: 'failed' (logged, not thrown)
17
+ * - Spawn event timeout (5 s) → status: 'failed' (logged, not thrown)
18
+ * - Spawn 'spawn' event → status: 'triggered' (resolved; child detached)
19
+ *
20
+ * Karpathy §1 (Think Before Coding): extracted from the QA verdict
21
+ * `verdict-rid-best-practice-scan-qa-ship.md` AC-1 PARTIAL finding. The
22
+ * `prd:handed-off` transition in `src/cli/commands/request-commands.ts`
23
+ * is the canonical hook point — peaks-prd's contract in
24
+ * `skills/bee/peaks-prd/SKILL.md` §"Step 2.5 sub-step" mandates this firing
25
+ * BEFORE peaks-rd takes over.
26
+ *
27
+ * Karpathy §2 (Simplicity First): single spawn, no retry, no async queue.
28
+ * Karpathy §3 (Surgical Changes): one new file, no other source files
29
+ * touched (the call site wiring in request-commands.ts is the only other
30
+ * edit; that is the "Pattern A: inline post-step" from the dispatch spec).
31
+ */
32
+ export type BestPracticeTriggerStatus = 'triggered' | 'skipped-empty-goal' | 'skipped-artifact-missing' | 'failed';
33
+ export type BestPracticeTriggerResult = {
34
+ readonly status: BestPracticeTriggerStatus;
35
+ readonly businessGoal?: string;
36
+ readonly pid?: number;
37
+ readonly reason?: string;
38
+ };
39
+ /** Extract the first non-empty bullet from the `## Goals` section of a PRD
40
+ * body. Falls back to the `raw input (sanitized):` line at the top when
41
+ * the Goals section is missing or empty. Returns null when no goal can
42
+ * be derived (caller should treat as `skipped-empty-goal`).
43
+ *
44
+ * Pure function — exported separately so unit tests can target the
45
+ * extraction logic without spawning child processes. */
46
+ export declare function extractBusinessGoal(prdBody: string): string | null;
47
+ /** Fire-and-forget: invoke `peaks best-practice-scan --project <root>
48
+ * --intent "<businessGoal>"`. Never throws — always resolves with a
49
+ * BestPracticeTriggerResult envelope. The child is detached via `unref()`
50
+ * on the 'spawn' event so the parent process is not blocked waiting for
51
+ * the BPS scan to finish (the scan itself sleeps ~100 ms in the v1
52
+ * Context7 stub).
53
+ *
54
+ * `PEAKS_BEST_PRACTICE_STDIN` is forced to empty so the BPS catch-gate
55
+ * auto-accepts the recommended option — auto-trigger is non-interactive
56
+ * by design (no user at the prompt when prd → handed-off fires). */
57
+ export declare function triggerBestPracticeScan(opts: {
58
+ readonly projectRoot: string;
59
+ readonly sessionId: string;
60
+ readonly requestId: string;
61
+ }): Promise<BestPracticeTriggerResult>;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Slice 2026-08-12 best-practice-scan — auto-trigger post-step (Slice F).
3
+ *
4
+ * When peaks-prd's request artifact transitions to `handed-off`, this helper
5
+ * extracts the `businessGoal` (first non-empty bullet under `## Goals` of the
6
+ * PRD body) and fires `peaks best-practice-scan --project <root> --intent "<goal>"`
7
+ * as a fire-and-forget post-step. The PRD → handed-off transition is the
8
+ * canonical moment when "businessGoal transitions to complete" (the prd state
9
+ * machine's terminal state).
10
+ *
11
+ * Failure modes (all non-fatal — the prd → handed-off transition MUST NOT
12
+ * block on BPS issues):
13
+ * - Empty businessGoal → status: 'skipped-empty-goal' (no spawn)
14
+ * - Artifact missing → status: 'skipped-artifact-missing'
15
+ * - Peaks binary missing → status: 'skipped-artifact-missing'
16
+ * - Spawn 'error' event → status: 'failed' (logged, not thrown)
17
+ * - Spawn event timeout (5 s) → status: 'failed' (logged, not thrown)
18
+ * - Spawn 'spawn' event → status: 'triggered' (resolved; child detached)
19
+ *
20
+ * Karpathy §1 (Think Before Coding): extracted from the QA verdict
21
+ * `verdict-rid-best-practice-scan-qa-ship.md` AC-1 PARTIAL finding. The
22
+ * `prd:handed-off` transition in `src/cli/commands/request-commands.ts`
23
+ * is the canonical hook point — peaks-prd's contract in
24
+ * `skills/bee/peaks-prd/SKILL.md` §"Step 2.5 sub-step" mandates this firing
25
+ * BEFORE peaks-rd takes over.
26
+ *
27
+ * Karpathy §2 (Simplicity First): single spawn, no retry, no async queue.
28
+ * Karpathy §3 (Surgical Changes): one new file, no other source files
29
+ * touched (the call site wiring in request-commands.ts is the only other
30
+ * edit; that is the "Pattern A: inline post-step" from the dispatch spec).
31
+ */
32
+ import { spawn } from 'node:child_process';
33
+ import { existsSync } from 'node:fs';
34
+ import { join } from 'node:path';
35
+ import { showRequestArtifact } from '../artifacts/request-artifact-service.js';
36
+ /** Extract the first non-empty bullet from the `## Goals` section of a PRD
37
+ * body. Falls back to the `raw input (sanitized):` line at the top when
38
+ * the Goals section is missing or empty. Returns null when no goal can
39
+ * be derived (caller should treat as `skipped-empty-goal`).
40
+ *
41
+ * Pure function — exported separately so unit tests can target the
42
+ * extraction logic without spawning child processes. */
43
+ export function extractBusinessGoal(prdBody) {
44
+ const goalsMatch = /##\s+Goals\s*\n([\s\S]*?)(?=\n##\s|\n#\s|\Z)/.exec(prdBody);
45
+ if (goalsMatch !== null && goalsMatch[1] !== undefined) {
46
+ for (const raw of goalsMatch[1].split(/\r?\n/)) {
47
+ const line = raw.replace(/^[\s>*\-]+/, '').trim();
48
+ if (line.length > 0 && line !== '...')
49
+ return line;
50
+ }
51
+ }
52
+ const rawInputMatch = /^- raw input \(sanitized\):\s*(.+)$/m.exec(prdBody);
53
+ if (rawInputMatch !== null && rawInputMatch[1] !== undefined) {
54
+ const line = rawInputMatch[1].trim();
55
+ if (line.length > 0 && line !== '...')
56
+ return line;
57
+ }
58
+ return null;
59
+ }
60
+ /** Fire-and-forget: invoke `peaks best-practice-scan --project <root>
61
+ * --intent "<businessGoal>"`. Never throws — always resolves with a
62
+ * BestPracticeTriggerResult envelope. The child is detached via `unref()`
63
+ * on the 'spawn' event so the parent process is not blocked waiting for
64
+ * the BPS scan to finish (the scan itself sleeps ~100 ms in the v1
65
+ * Context7 stub).
66
+ *
67
+ * `PEAKS_BEST_PRACTICE_STDIN` is forced to empty so the BPS catch-gate
68
+ * auto-accepts the recommended option — auto-trigger is non-interactive
69
+ * by design (no user at the prompt when prd → handed-off fires). */
70
+ export async function triggerBestPracticeScan(opts) {
71
+ const artifact = await showRequestArtifact({
72
+ projectRoot: opts.projectRoot,
73
+ role: 'prd',
74
+ requestId: opts.requestId,
75
+ sessionId: opts.sessionId
76
+ });
77
+ if (artifact === null) {
78
+ return { status: 'skipped-artifact-missing' };
79
+ }
80
+ const businessGoal = extractBusinessGoal(artifact.content);
81
+ if (businessGoal === null) {
82
+ return { status: 'skipped-empty-goal' };
83
+ }
84
+ const peaksBin = join(opts.projectRoot, 'bin', 'peaks.js');
85
+ if (!existsSync(peaksBin)) {
86
+ return { status: 'skipped-artifact-missing', reason: `peaks binary not found at ${peaksBin}` };
87
+ }
88
+ return await new Promise((resolveTrigger) => {
89
+ let resolved = false;
90
+ const resolveOnce = (result) => {
91
+ if (resolved)
92
+ return;
93
+ resolved = true;
94
+ resolveTrigger(result);
95
+ };
96
+ const child = spawn(process.execPath, [
97
+ peaksBin,
98
+ 'best-practice-scan',
99
+ '--project',
100
+ opts.projectRoot,
101
+ '--intent',
102
+ businessGoal,
103
+ '--json'
104
+ ], {
105
+ cwd: opts.projectRoot,
106
+ stdio: 'ignore',
107
+ detached: true,
108
+ env: { ...process.env, PEAKS_BEST_PRACTICE_STDIN: '' }
109
+ });
110
+ child.once('error', (err) => {
111
+ resolveOnce({ status: 'failed', businessGoal, reason: err.message });
112
+ });
113
+ child.once('spawn', () => {
114
+ if (typeof child.unref === 'function')
115
+ child.unref();
116
+ resolveOnce({
117
+ status: 'triggered',
118
+ businessGoal,
119
+ pid: typeof child.pid === 'number' ? child.pid : -1
120
+ });
121
+ });
122
+ setTimeout(() => {
123
+ resolveOnce({ status: 'failed', businessGoal, reason: 'spawn event timeout (5s)' });
124
+ }, 5_000).unref();
125
+ });
126
+ }