create-principles-disciple 1.77.0 → 1.78.0

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 (184) hide show
  1. package/console/dist/server/config/feature-flags.d.ts +25 -0
  2. package/console/dist/server/config/feature-flags.js +101 -0
  3. package/console/dist/server/index.js +16 -0
  4. package/console/dist/server/models/FeedbackReportConsoleModel.d.ts +49 -0
  5. package/console/dist/server/models/FeedbackReportConsoleModel.js +242 -0
  6. package/console/dist/server/routes/feedback-reports.d.ts +18 -0
  7. package/console/dist/server/routes/feedback-reports.js +165 -0
  8. package/console/dist/ui/App.js +2 -1
  9. package/console/dist/ui/api.d.ts +26 -2
  10. package/console/dist/ui/api.js +18 -1
  11. package/console/dist/ui/components/app-sidebar.js +2 -1
  12. package/console/dist/ui/components/error-boundary.js +9 -2
  13. package/console/dist/ui/i18n/en.json +67 -1
  14. package/console/dist/ui/i18n/zh-CN.json +67 -1
  15. package/console/dist/ui/pages/ReportProblemPage.d.ts +1 -0
  16. package/console/dist/ui/pages/ReportProblemPage.js +186 -0
  17. package/console/dist/ui/pages/ReportProblemValidators.d.ts +51 -0
  18. package/console/dist/ui/pages/ReportProblemValidators.js +114 -0
  19. package/console/dist/web/assets/app.css +18 -0
  20. package/console/dist/web/assets/app.js +1047 -125
  21. package/console/package.json +1 -0
  22. package/core/dist/runtime-v2/__tests__/architecture-regression.test.js +23 -1
  23. package/core/dist/runtime-v2/__tests__/architecture-regression.test.js.map +1 -1
  24. package/core/dist/runtime-v2/__tests__/feedback/create-report.test.d.ts +2 -0
  25. package/core/dist/runtime-v2/__tests__/feedback/create-report.test.d.ts.map +1 -0
  26. package/core/dist/runtime-v2/__tests__/feedback/create-report.test.js +402 -0
  27. package/core/dist/runtime-v2/__tests__/feedback/create-report.test.js.map +1 -0
  28. package/core/dist/runtime-v2/__tests__/feedback/privacy-preview.test.d.ts +2 -0
  29. package/core/dist/runtime-v2/__tests__/feedback/privacy-preview.test.d.ts.map +1 -0
  30. package/core/dist/runtime-v2/__tests__/feedback/privacy-preview.test.js +128 -0
  31. package/core/dist/runtime-v2/__tests__/feedback/privacy-preview.test.js.map +1 -0
  32. package/core/dist/runtime-v2/__tests__/feedback/redact-sensitive.test.d.ts +2 -0
  33. package/core/dist/runtime-v2/__tests__/feedback/redact-sensitive.test.d.ts.map +1 -0
  34. package/core/dist/runtime-v2/__tests__/feedback/redact-sensitive.test.js +231 -0
  35. package/core/dist/runtime-v2/__tests__/feedback/redact-sensitive.test.js.map +1 -0
  36. package/core/dist/runtime-v2/__tests__/feedback/render-github-url.test.d.ts +2 -0
  37. package/core/dist/runtime-v2/__tests__/feedback/render-github-url.test.d.ts.map +1 -0
  38. package/core/dist/runtime-v2/__tests__/feedback/render-github-url.test.js +102 -0
  39. package/core/dist/runtime-v2/__tests__/feedback/render-github-url.test.js.map +1 -0
  40. package/core/dist/runtime-v2/__tests__/feedback/render-markdown.test.d.ts +2 -0
  41. package/core/dist/runtime-v2/__tests__/feedback/render-markdown.test.d.ts.map +1 -0
  42. package/core/dist/runtime-v2/__tests__/feedback/render-markdown.test.js +130 -0
  43. package/core/dist/runtime-v2/__tests__/feedback/render-markdown.test.js.map +1 -0
  44. package/core/dist/runtime-v2/activation/__tests__/prompt-activation-reader-contract.test.d.ts +2 -0
  45. package/core/dist/runtime-v2/activation/__tests__/prompt-activation-reader-contract.test.d.ts.map +1 -0
  46. package/core/dist/runtime-v2/activation/__tests__/prompt-activation-reader-contract.test.js +343 -0
  47. package/core/dist/runtime-v2/activation/__tests__/prompt-activation-reader-contract.test.js.map +1 -0
  48. package/core/dist/runtime-v2/activation/index.d.ts +2 -0
  49. package/core/dist/runtime-v2/activation/index.d.ts.map +1 -1
  50. package/core/dist/runtime-v2/activation/index.js +1 -0
  51. package/core/dist/runtime-v2/activation/index.js.map +1 -1
  52. package/core/dist/runtime-v2/activation/prompt-activation-reader-contract.d.ts +34 -0
  53. package/core/dist/runtime-v2/activation/prompt-activation-reader-contract.d.ts.map +1 -0
  54. package/core/dist/runtime-v2/activation/prompt-activation-reader-contract.js +109 -0
  55. package/core/dist/runtime-v2/activation/prompt-activation-reader-contract.js.map +1 -0
  56. package/core/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js +13 -0
  57. package/core/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js.map +1 -1
  58. package/core/dist/runtime-v2/feature-flags/__tests__/plugin-surface-registry.test.d.ts +2 -0
  59. package/core/dist/runtime-v2/feature-flags/__tests__/plugin-surface-registry.test.d.ts.map +1 -0
  60. package/core/dist/runtime-v2/feature-flags/__tests__/plugin-surface-registry.test.js +141 -0
  61. package/core/dist/runtime-v2/feature-flags/__tests__/plugin-surface-registry.test.js.map +1 -0
  62. package/core/dist/runtime-v2/feature-flags/feature-flag-contract.d.ts.map +1 -1
  63. package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js +4 -1
  64. package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
  65. package/core/dist/runtime-v2/feature-flags/index.d.ts +2 -0
  66. package/core/dist/runtime-v2/feature-flags/index.d.ts.map +1 -1
  67. package/core/dist/runtime-v2/feature-flags/index.js +1 -0
  68. package/core/dist/runtime-v2/feature-flags/index.js.map +1 -1
  69. package/core/dist/runtime-v2/feature-flags/plugin-surface-registry.d.ts +25 -0
  70. package/core/dist/runtime-v2/feature-flags/plugin-surface-registry.d.ts.map +1 -0
  71. package/core/dist/runtime-v2/feature-flags/plugin-surface-registry.js +233 -0
  72. package/core/dist/runtime-v2/feature-flags/plugin-surface-registry.js.map +1 -0
  73. package/core/dist/runtime-v2/feedback/create-report.d.ts +14 -0
  74. package/core/dist/runtime-v2/feedback/create-report.d.ts.map +1 -0
  75. package/core/dist/runtime-v2/feedback/create-report.js +257 -0
  76. package/core/dist/runtime-v2/feedback/create-report.js.map +1 -0
  77. package/core/dist/runtime-v2/feedback/feedback-types.d.ts +120 -0
  78. package/core/dist/runtime-v2/feedback/feedback-types.d.ts.map +1 -0
  79. package/core/dist/runtime-v2/feedback/feedback-types.js +243 -0
  80. package/core/dist/runtime-v2/feedback/feedback-types.js.map +1 -0
  81. package/core/dist/runtime-v2/feedback/index.d.ts +9 -0
  82. package/core/dist/runtime-v2/feedback/index.d.ts.map +1 -0
  83. package/core/dist/runtime-v2/feedback/index.js +11 -0
  84. package/core/dist/runtime-v2/feedback/index.js.map +1 -0
  85. package/core/dist/runtime-v2/feedback/internal-guards.d.ts +2 -0
  86. package/core/dist/runtime-v2/feedback/internal-guards.d.ts.map +1 -0
  87. package/core/dist/runtime-v2/feedback/internal-guards.js +6 -0
  88. package/core/dist/runtime-v2/feedback/internal-guards.js.map +1 -0
  89. package/core/dist/runtime-v2/feedback/privacy-preview.d.ts +14 -0
  90. package/core/dist/runtime-v2/feedback/privacy-preview.d.ts.map +1 -0
  91. package/core/dist/runtime-v2/feedback/privacy-preview.js +128 -0
  92. package/core/dist/runtime-v2/feedback/privacy-preview.js.map +1 -0
  93. package/core/dist/runtime-v2/feedback/redact-sensitive.d.ts +37 -0
  94. package/core/dist/runtime-v2/feedback/redact-sensitive.d.ts.map +1 -0
  95. package/core/dist/runtime-v2/feedback/redact-sensitive.js +272 -0
  96. package/core/dist/runtime-v2/feedback/redact-sensitive.js.map +1 -0
  97. package/core/dist/runtime-v2/feedback/render-github-url.d.ts +21 -0
  98. package/core/dist/runtime-v2/feedback/render-github-url.d.ts.map +1 -0
  99. package/core/dist/runtime-v2/feedback/render-github-url.js +57 -0
  100. package/core/dist/runtime-v2/feedback/render-github-url.js.map +1 -0
  101. package/core/dist/runtime-v2/feedback/render-markdown.d.ts +12 -0
  102. package/core/dist/runtime-v2/feedback/render-markdown.d.ts.map +1 -0
  103. package/core/dist/runtime-v2/feedback/render-markdown.js +166 -0
  104. package/core/dist/runtime-v2/feedback/render-markdown.js.map +1 -0
  105. package/core/dist/runtime-v2/feedback/safe-stringify.d.ts +6 -0
  106. package/core/dist/runtime-v2/feedback/safe-stringify.d.ts.map +1 -0
  107. package/core/dist/runtime-v2/feedback/safe-stringify.js +115 -0
  108. package/core/dist/runtime-v2/feedback/safe-stringify.js.map +1 -0
  109. package/core/dist/runtime-v2/index.d.ts +6 -1
  110. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  111. package/core/dist/runtime-v2/index.js +10 -1
  112. package/core/dist/runtime-v2/index.js.map +1 -1
  113. package/core/package.json +4 -0
  114. package/package.json +1 -1
  115. package/plugin/dist/commands/context.js +5 -5
  116. package/plugin/dist/commands/strategy.d.ts +0 -1
  117. package/plugin/dist/commands/strategy.js +0 -14
  118. package/plugin/dist/core/runtime-v2-prompt-activation-reader.d.ts +4 -13
  119. package/plugin/dist/core/runtime-v2-prompt-activation-reader.js +36 -74
  120. package/plugin/dist/core/surface-guard.d.ts +27 -0
  121. package/plugin/dist/core/surface-guard.js +82 -0
  122. package/plugin/dist/hooks/prompt.js +20 -143
  123. package/plugin/dist/i18n/commands.js +2 -14
  124. package/plugin/dist/index.d.ts +27 -0
  125. package/plugin/dist/index.js +174 -55
  126. package/plugin/dist/service/correction-observer-service.d.ts +11 -0
  127. package/plugin/dist/service/correction-observer-service.js +172 -0
  128. package/plugin/dist/service/evolution-worker.js +1 -110
  129. package/plugin/dist/types.d.ts +2 -2
  130. package/plugin/dist/types.js +3 -3
  131. package/plugin/templates/langs/en/core/BOOTSTRAP.md +4 -18
  132. package/plugin/templates/langs/en/skills/bootstrap-tools/SKILL.md +1 -1
  133. package/plugin/templates/langs/en/skills/init-strategy/SKILL.md +1 -1
  134. package/plugin/templates/langs/en/skills/pd-mentor/SKILL.md +8 -23
  135. package/plugin/templates/langs/zh/core/BOOTSTRAP.md +2 -15
  136. package/plugin/templates/langs/zh/skills/bootstrap-tools/SKILL.md +1 -1
  137. package/plugin/templates/langs/zh/skills/init-strategy/SKILL.md +1 -1
  138. package/plugin/templates/langs/zh/skills/pd-mentor/SKILL.md +7 -22
  139. package/plugin/templates/langs/en/skills/ai-sprint-orchestration/EXAMPLES.md +0 -63
  140. package/plugin/templates/langs/en/skills/ai-sprint-orchestration/REFERENCE.md +0 -136
  141. package/plugin/templates/langs/en/skills/ai-sprint-orchestration/SKILL.md +0 -67
  142. package/plugin/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +0 -143
  143. package/plugin/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +0 -107
  144. package/plugin/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +0 -107
  145. package/plugin/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +0 -95
  146. package/plugin/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +0 -98
  147. package/plugin/templates/langs/en/skills/ai-sprint-orchestration/references/workflow-v1-acceptance-checklist.md +0 -58
  148. package/plugin/templates/langs/en/skills/ai-sprint-orchestration/references/workflow-v1.4-work-unit-handoff.md +0 -190
  149. package/plugin/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/archive.mjs +0 -310
  150. package/plugin/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/contract-enforcement.mjs +0 -683
  151. package/plugin/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/decision.mjs +0 -604
  152. package/plugin/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/state-store.mjs +0 -32
  153. package/plugin/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/task-specs.mjs +0 -707
  154. package/plugin/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +0 -3455
  155. package/plugin/templates/langs/en/skills/evolve-system/SKILL.md +0 -46
  156. package/plugin/templates/langs/en/skills/manage-okr/SKILL.md +0 -96
  157. package/plugin/templates/langs/en/skills/pd-daily/SKILL.md +0 -199
  158. package/plugin/templates/langs/en/skills/pd-grooming/SKILL.md +0 -46
  159. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/EXAMPLES.md +0 -63
  160. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/REFERENCE.md +0 -136
  161. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/SKILL.md +0 -67
  162. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +0 -143
  163. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +0 -107
  164. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +0 -107
  165. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +0 -111
  166. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +0 -95
  167. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +0 -98
  168. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/references/workflow-v1-acceptance-checklist.md +0 -58
  169. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/references/workflow-v1.4-work-unit-handoff.md +0 -190
  170. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/runtime/.gitignore +0 -2
  171. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/archive.mjs +0 -310
  172. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/contract-enforcement.mjs +0 -683
  173. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/decision.mjs +0 -604
  174. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/state-store.mjs +0 -32
  175. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/task-specs.mjs +0 -707
  176. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +0 -3455
  177. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/test/archive.test.mjs +0 -230
  178. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/test/contract-enforcement.test.mjs +0 -672
  179. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/test/decision.test.mjs +0 -1321
  180. package/plugin/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +0 -1435
  181. package/plugin/templates/langs/zh/skills/evolve-system/SKILL.md +0 -46
  182. package/plugin/templates/langs/zh/skills/manage-okr/SKILL.md +0 -109
  183. package/plugin/templates/langs/zh/skills/pd-daily/SKILL.md +0 -283
  184. package/plugin/templates/langs/zh/skills/pd-grooming/SKILL.md +0 -46
@@ -1,4 +1,7 @@
1
1
  import * as path from 'path';
2
+ import * as fs from 'fs';
3
+ import * as yaml from 'js-yaml';
4
+ import { computeEffectiveFlags, DEFAULT_FEATURE_FLAGS } from '@principles/core/runtime-v2';
2
5
  import { classifyTask } from './core/local-worker-routing.js';
3
6
  import { completeShadowObservation, recordShadowRouting } from './core/shadow-observation-registry.js';
4
7
  import { getCommandDescription } from './i18n/commands.js';
@@ -10,7 +13,7 @@ import { handleBeforeReset, handleBeforeCompaction, handleAfterCompaction } from
10
13
  import { handleLlmOutput } from './hooks/llm.js';
11
14
  import { handleSubagentEnded } from './hooks/subagent.js';
12
15
  import * as TrajectoryCollector from './hooks/trajectory-collector.js';
13
- import { handleInitStrategy, handleManageOkr } from './commands/strategy.js';
16
+ import { handleInitStrategy } from './commands/strategy.js';
14
17
  import { handleBootstrapTools, handleResearchTools } from './commands/capabilities.js';
15
18
  import { handleThinkingOs } from './commands/thinking-os.js';
16
19
  import { handlePainCommand, handlePainReportCommand } from './commands/pain.js';
@@ -27,6 +30,7 @@ import { handleExportCommand } from './commands/export.js';
27
30
  import { handleSamplesCommand } from './commands/samples.js';
28
31
  import { handleWorkflowDebugCommand } from './commands/workflow-debug.js';
29
32
  import { EvolutionWorkerService } from './service/evolution-worker.js';
33
+ import { CorrectionObserverService } from './service/correction-observer-service.js';
30
34
  import { TrajectoryService } from './service/trajectory-service.js';
31
35
  import { PDTaskService } from './core/pd-task-service.js';
32
36
  import { CentralSyncService } from './service/central-sync-service.js';
@@ -39,6 +43,7 @@ import { resolveCommandWorkspaceDir, resolveToolHookWorkspaceDirSafe } from './u
39
43
  import { computeRuntimeShadowTaskFingerprint, PD_LOCAL_PROFILES } from './utils/shadow-fingerprint.js';
40
44
  import { validateWorkspaceDir } from './core/workspace-dir-validation.js';
41
45
  import { resolveWorkspaceDirFromApi } from './core/path-resolver.js';
46
+ import { checkSurfaceGuard, guardHook, guardService } from './core/surface-guard.js';
42
47
  // Track started workspaces — one-time init + evolution worker per workspace
43
48
  const startedWorkspaces = new Set();
44
49
  const HOOK_WORKSPACE_RESOLUTION_NEXT_ACTION = 'verify gateway plugin activation and hook workspace binding; ' +
@@ -46,6 +51,89 @@ const HOOK_WORKSPACE_RESOLUTION_NEXT_ACTION = 'verify gateway plugin activation
46
51
  // Map from childSessionKey → shadowObservationId
47
52
  // Used to complete shadow observations when subagent ends
48
53
  const pendingShadowObservations = new Map();
54
+ // ── Feature Flag Loader (plugin I/O boundary) ─────────────────────────────
55
+ // Reads workspace feature-flags.yaml and checks a specific flag.
56
+ // Returns the flag definition with effective enabled state.
57
+ const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
58
+ function isRecord(value) {
59
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
60
+ }
61
+ function loadFeatureFlagFromWorkspace(workspaceDir, flagId, logger) {
62
+ const configPath = path.join(workspaceDir, '.pd', 'feature-flags.yaml');
63
+ if (!fs.existsSync(configPath)) {
64
+ const flags = computeEffectiveFlags({}, DEFAULT_FEATURE_FLAGS, configPath);
65
+ const flag = flags.flags[flagId];
66
+ return { enabled: flag?.enabled ?? false, source: 'defaults' };
67
+ }
68
+ let raw;
69
+ try {
70
+ raw = fs.readFileSync(configPath, 'utf8');
71
+ }
72
+ catch (e) {
73
+ const msg = e instanceof Error ? e.message : String(e);
74
+ logger?.warn?.(`[PD:FeatureFlags] Feature flags unreadable: ${msg} — using defaults`);
75
+ const flags = computeEffectiveFlags({}, DEFAULT_FEATURE_FLAGS, configPath);
76
+ const flag = flags.flags[flagId];
77
+ return { enabled: flag?.enabled ?? false, source: 'defaults' };
78
+ }
79
+ let parsed;
80
+ try {
81
+ parsed = yaml.load(raw, { schema: yaml.JSON_SCHEMA });
82
+ }
83
+ catch (e) {
84
+ const parseMsg = e instanceof Error ? e.message : String(e);
85
+ logger?.warn?.(`[PD:FeatureFlags] Feature flags YAML parse error: ${parseMsg} — using defaults`);
86
+ const flags = computeEffectiveFlags({}, DEFAULT_FEATURE_FLAGS, configPath);
87
+ const flag = flags.flags[flagId];
88
+ return { enabled: flag?.enabled ?? false, source: 'defaults' };
89
+ }
90
+ if (!isRecord(parsed)) {
91
+ logger?.warn?.(`[PD:FeatureFlags] Feature flags not a mapping — using defaults`);
92
+ const flags = computeEffectiveFlags({}, DEFAULT_FEATURE_FLAGS, configPath);
93
+ const flag = flags.flags[flagId];
94
+ return { enabled: flag?.enabled ?? false, source: 'defaults' };
95
+ }
96
+ // parsed is now narrowed to Record<string, unknown> by isRecord guard
97
+ const parsedRecord = Object.create(null);
98
+ for (const key of Object.keys(parsed)) {
99
+ if (DANGEROUS_KEYS.has(key))
100
+ continue;
101
+ if (Object.hasOwn(parsed, key)) {
102
+ parsedRecord[key] = parsed[key];
103
+ }
104
+ }
105
+ const flags = computeEffectiveFlags(parsedRecord, DEFAULT_FEATURE_FLAGS, configPath);
106
+ const flag = flags.flags[flagId];
107
+ return { enabled: flag?.enabled ?? false, source: flags.source };
108
+ }
109
+ export function shouldStartEvolutionWorker(workspaceDir, logger) {
110
+ const flag = loadFeatureFlagFromWorkspace(workspaceDir, 'evolution_worker', logger);
111
+ if (flag.enabled) {
112
+ return { shouldStart: true, flagSource: flag.source, disabledInfo: null };
113
+ }
114
+ const disabledInfo = JSON.stringify({
115
+ reason: 'mvp_quiet_per_adr0014',
116
+ nextAction: 'set evolution_worker.enabled=true in .pd/feature-flags.yaml to enable',
117
+ featureFlag: 'evolution_worker',
118
+ boundedContext: 'legacy_evolution_worker',
119
+ flagSource: flag.source,
120
+ });
121
+ return { shouldStart: false, flagSource: flag.source, disabledInfo };
122
+ }
123
+ export function shouldStartCorrectionObserver(workspaceDir, logger) {
124
+ const flag = loadFeatureFlagFromWorkspace(workspaceDir, 'correction_observer', logger);
125
+ if (flag.enabled) {
126
+ return { shouldStart: true, flagSource: flag.source, disabledInfo: null };
127
+ }
128
+ const disabledInfo = JSON.stringify({
129
+ reason: 'correction_observer_disabled',
130
+ nextAction: 'set correction_observer.enabled=true in .pd/feature-flags.yaml to enable',
131
+ featureFlag: 'correction_observer',
132
+ boundedContext: 'correction_observer_service',
133
+ flagSource: flag.source,
134
+ });
135
+ return { shouldStart: false, flagSource: flag.source, disabledInfo };
136
+ }
49
137
  const plugin = {
50
138
  name: "Principles Disciple",
51
139
  description: "Evolutionary programming agent framework with strategic guardrails and reflection loops.",
@@ -67,9 +155,21 @@ const plugin = {
67
155
  }
68
156
  }, 1000);
69
157
  healthCheckTimer.unref(); // Don't keep process alive for health check
158
+ // ── MVP Surface Guard (PRI-289): Verify surface classification ──
159
+ const surfaceGuard = checkSurfaceGuard();
160
+ if (!surfaceGuard.passed) {
161
+ for (const violation of surfaceGuard.violations) {
162
+ api.logger.error(`[PD:surface-guard] VIOLATION: ${violation}`);
163
+ }
164
+ }
165
+ api.logger.info(`[PD:surface-guard] Core surfaces: ${surfaceGuard.enabledCoreSurfaces.join(', ')}`);
166
+ api.logger.info(`[PD:surface-guard] Disabled non-core surfaces: ${surfaceGuard.disabledNonCoreSurfaces.length}`);
167
+ for (const warning of surfaceGuard.warnings) {
168
+ api.logger.warn(`[PD:surface-guard] ${warning}`);
169
+ }
70
170
  const language = api.pluginConfig?.language || 'en';
71
171
  // ── Hook: Prompt Building ──
72
- api.on('before_prompt_build', async (event, ctx) => {
172
+ api.on('before_prompt_build', guardHook('hook:before_prompt_build', api.logger, async (event, ctx) => {
73
173
  const workspaceDir = resolveToolHookWorkspaceDirSafe(ctx, api, 'before_prompt_build');
74
174
  if (!workspaceDir) {
75
175
  api.logger.error(`[PD:before_prompt_build] workspaceDir resolution failed. ` +
@@ -87,16 +187,39 @@ const plugin = {
87
187
  ensureWorkspaceTemplates(api, workspaceDir, language);
88
188
  SystemLogger.log(workspaceDir, 'SYSTEM_BOOT', `Principles Disciple online. Language: ${language}`);
89
189
  // ── Start EvolutionWorker for THIS workspace ──
90
- // One EvolutionWorker per workspace so each agent's pain signals
91
- // are processed independently.
92
- EvolutionWorkerService.api = api;
93
- EvolutionWorkerService.start({
94
- config: api.config,
95
- workspaceDir,
96
- stateDir: path.join(workspaceDir, '.state'),
97
- logger: api.logger,
98
- });
99
- api.logger.info(`[PD] EvolutionWorker started for workspace: ${workspaceDir}`);
190
+ // Gated behind evolution_worker feature flag (MVP-Quiet, default OFF per ADR-0014).
191
+ const gate = shouldStartEvolutionWorker(workspaceDir, api.logger);
192
+ if (gate.shouldStart) {
193
+ EvolutionWorkerService.api = api;
194
+ EvolutionWorkerService.start({
195
+ config: api.config,
196
+ workspaceDir,
197
+ stateDir: path.join(workspaceDir, '.state'),
198
+ logger: api.logger,
199
+ });
200
+ api.logger.info(`[PD] EvolutionWorker started for workspace: ${workspaceDir} (flag source: ${gate.flagSource})`);
201
+ }
202
+ else {
203
+ // Structured observability per ERR-002: no silent skip
204
+ api.logger.info(`[PD] EvolutionWorker NOT started for workspace: ${workspaceDir}. ${gate.disabledInfo}`);
205
+ SystemLogger.log(workspaceDir, 'EVOLUTION_WORKER_DISABLED', gate.disabledInfo ?? '');
206
+ }
207
+ // ── Start CorrectionObserver for THIS workspace ──
208
+ // MVP-Core per ADR-0014 amendment, independently owned (PRI-293).
209
+ const corrGate = shouldStartCorrectionObserver(workspaceDir, api.logger);
210
+ if (corrGate.shouldStart) {
211
+ CorrectionObserverService.start({
212
+ config: api.config,
213
+ workspaceDir,
214
+ stateDir: path.join(workspaceDir, '.state'),
215
+ logger: api.logger,
216
+ });
217
+ api.logger.info(`[PD] CorrectionObserver started for workspace: ${workspaceDir} (flag source: ${corrGate.flagSource})`);
218
+ }
219
+ else {
220
+ api.logger.info(`[PD] CorrectionObserver NOT started for workspace: ${workspaceDir}. ${corrGate.disabledInfo}`);
221
+ SystemLogger.log(workspaceDir, 'CORRECTION_OBSERVER_DISABLED', corrGate.disabledInfo ?? '');
222
+ }
100
223
  }
101
224
  const result = await handleBeforePromptBuild(event, { ...ctx, api: api, workspaceDir });
102
225
  // Record success
@@ -114,9 +237,9 @@ const plugin = {
114
237
  });
115
238
  api.logger.error(`[PD] Error in before_prompt_build: ${String(err)}`);
116
239
  }
117
- });
240
+ }));
118
241
  // ── Hook: Security Gate ──
119
- api.on('before_tool_call', (event, ctx) => {
242
+ api.on('before_tool_call', guardHook('hook:before_tool_call', api.logger, (event, ctx) => {
120
243
  const workspaceDir = resolveToolHookWorkspaceDirSafe(ctx, api, 'before_tool_call');
121
244
  if (!workspaceDir) {
122
245
  api.logger.error(`[PD:before_tool_call] workspaceDir resolution failed. ` +
@@ -142,9 +265,9 @@ const plugin = {
142
265
  }, { flushImmediately: true });
143
266
  api.logger.error(`[PD] Error in before_tool_call: ${String(err)}`);
144
267
  }
145
- });
268
+ }));
146
269
  // ── Hook: Pain & Trust ──
147
- api.on('after_tool_call', (event, ctx) => {
270
+ api.on('after_tool_call', guardHook('hook:after_tool_call', api.logger, (event, ctx) => {
148
271
  const workspaceDir = resolveToolHookWorkspaceDirSafe(ctx, api, 'after_tool_call');
149
272
  if (!workspaceDir) {
150
273
  api.logger.error(`[PD:after_tool_call] workspaceDir resolution failed. ` +
@@ -169,9 +292,9 @@ const plugin = {
169
292
  }, { flushImmediately: true });
170
293
  api.logger.error(`[PD:EmpathyObserver] Error in after_tool_call: ${String(err)}`);
171
294
  }
172
- });
295
+ }));
173
296
  // ── Hook: LLM Analysis ──
174
- api.on('llm_output', (event, ctx) => {
297
+ api.on('llm_output', guardHook('hook:llm_output', api.logger, (event, ctx) => {
175
298
  const workspaceDir = resolveToolHookWorkspaceDirSafe(ctx, api, 'llm_output');
176
299
  if (!workspaceDir) {
177
300
  api.logger.error(`[PD:llm_output] workspaceDir resolution failed. ` +
@@ -196,10 +319,10 @@ const plugin = {
196
319
  });
197
320
  api.logger.error(`[PD] Error in llm_output: ${String(err)}`);
198
321
  }
199
- });
322
+ }));
200
323
  // ── Hook: Trajectory Collection (Behavior Evolution Phase 0) ──
201
324
  // Note: after_tool_call and llm_output are safe to collect
202
- api.on('after_tool_call', (event, ctx) => {
325
+ api.on('after_tool_call', guardHook('hook:after_tool_call.trajectory', api.logger, (event, ctx) => {
203
326
  try {
204
327
  const workspaceDir = resolveToolHookWorkspaceDirSafe(ctx, api, 'trajectory.after_tool_call');
205
328
  if (!workspaceDir)
@@ -210,8 +333,8 @@ const plugin = {
210
333
  catch (_err) {
211
334
  // Non-critical: don't log, just skip
212
335
  }
213
- });
214
- api.on('llm_output', (event, ctx) => {
336
+ }));
337
+ api.on('llm_output', guardHook('hook:llm_output.trajectory', api.logger, (event, ctx) => {
215
338
  try {
216
339
  const workspaceDir = resolveToolHookWorkspaceDirSafe(ctx, api, 'trajectory.llm_output');
217
340
  if (!workspaceDir)
@@ -222,9 +345,9 @@ const plugin = {
222
345
  catch (_err) {
223
346
  // Non-critical: don't log, just skip
224
347
  }
225
- });
348
+ }));
226
349
  // ── Hook: Subagent Loop Closure ──
227
- api.on('subagent_spawning', (event, _ctx) => {
350
+ api.on('subagent_spawning', guardHook('hook:subagent_spawning', api.logger, (event, _ctx) => {
228
351
  try {
229
352
  // FIX (B): Never fall back to '.' — fail-fast with ERROR log if workspaceDir cannot be resolved.
230
353
  // For subagent hooks, we use event.agentId as the target agent for workspace resolution.
@@ -262,8 +385,8 @@ const plugin = {
262
385
  api.logger.error(`[PD] Error in subagent_spawning shadow routing: ${String(err)}`);
263
386
  return { status: 'ok' }; // Don't block spawn on shadow observation errors
264
387
  }
265
- });
266
- api.on('subagent_ended', (event, ctx) => {
388
+ }));
389
+ api.on('subagent_ended', guardHook('hook:subagent_ended', api.logger, (event, ctx) => {
267
390
  try {
268
391
  // FIX (B): Never fall back to '.' — fail-fast with ERROR log if workspaceDir cannot be resolved.
269
392
  const workspaceDir = resolveWorkspaceDirFromApi(api, undefined);
@@ -297,9 +420,9 @@ const plugin = {
297
420
  catch (err) {
298
421
  api.logger.error(`[PD] Error in subagent_ended: ${String(err)}`);
299
422
  }
300
- });
423
+ }));
301
424
  // ── Hook: Lifecycle ──
302
- api.on('before_reset', (event, ctx) => {
425
+ api.on('before_reset', guardHook('hook:before_reset', api.logger, (event, ctx) => {
303
426
  const workspaceDir = resolveToolHookWorkspaceDirSafe(ctx, api, 'before_reset');
304
427
  if (!workspaceDir) {
305
428
  api.logger.error(`[PD:before_reset] workspaceDir resolution failed. ` +
@@ -308,8 +431,8 @@ const plugin = {
308
431
  return;
309
432
  }
310
433
  return handleBeforeReset(event, { ...ctx, workspaceDir });
311
- });
312
- api.on('before_compaction', (event, ctx) => {
434
+ }));
435
+ api.on('before_compaction', guardHook('hook:before_compaction', api.logger, (event, ctx) => {
313
436
  const workspaceDir = resolveToolHookWorkspaceDirSafe(ctx, api, 'before_compaction');
314
437
  if (!workspaceDir) {
315
438
  api.logger.error(`[PD:before_compaction] workspaceDir resolution failed. ` +
@@ -318,8 +441,8 @@ const plugin = {
318
441
  return;
319
442
  }
320
443
  return handleBeforeCompaction(event, { ...ctx, workspaceDir });
321
- });
322
- api.on('after_compaction', (event, ctx) => {
444
+ }));
445
+ api.on('after_compaction', guardHook('hook:after_compaction', api.logger, (event, ctx) => {
323
446
  const workspaceDir = resolveToolHookWorkspaceDirSafe(ctx, api, 'after_compaction');
324
447
  if (!workspaceDir) {
325
448
  api.logger.error(`[PD:after_compaction] workspaceDir resolution failed. ` +
@@ -328,17 +451,28 @@ const plugin = {
328
451
  return;
329
452
  }
330
453
  return handleAfterCompaction(event, { ...ctx, workspaceDir });
331
- });
454
+ }));
332
455
  // ── Service: Background Evolution Worker ──
333
456
  try {
334
457
  EvolutionWorkerService.api = api;
335
- api.registerService(EvolutionWorkerService);
336
- api.registerService(TrajectoryService);
337
- api.registerService(PDTaskService);
338
- api.registerService(CentralSyncService);
458
+ const guardedEvolutionWorker = guardService('service:evolution-worker', EvolutionWorkerService, api.logger);
459
+ if (guardedEvolutionWorker)
460
+ api.registerService(guardedEvolutionWorker);
461
+ const guardedCorrectionObserver = guardService('service:correction-observer', CorrectionObserverService, api.logger);
462
+ if (guardedCorrectionObserver)
463
+ api.registerService(guardedCorrectionObserver);
464
+ const guardedTrajectory = guardService('service:trajectory', TrajectoryService, api.logger);
465
+ if (guardedTrajectory)
466
+ api.registerService(guardedTrajectory);
467
+ const guardedPdTask = guardService('service:pd-task', PDTaskService, api.logger);
468
+ if (guardedPdTask)
469
+ api.registerService(guardedPdTask);
470
+ const guardedCentralSync = guardService('service:central-sync', CentralSyncService, api.logger);
471
+ if (guardedCentralSync)
472
+ api.registerService(guardedCentralSync);
339
473
  }
340
474
  catch (err) {
341
- api.logger.error(`[PD] Failed to register EvolutionWorkerService: ${String(err)}`);
475
+ api.logger.error(`[PD] Failed to register services: ${String(err)}`);
342
476
  }
343
477
  // ── Slash Commands ──
344
478
  // Register command with optional short alias
@@ -359,20 +493,9 @@ const plugin = {
359
493
  }
360
494
  };
361
495
  registerCommandWithAlias('pd-init', 'pdi', getCommandDescription('pd-init', language), (ctx) => handleInitStrategy(ctx));
362
- registerCommandWithAlias('pd-okr', 'pdk', getCommandDescription('pd-okr', language), (ctx) => handleManageOkr(ctx));
363
496
  registerCommandWithAlias('pd-bootstrap', 'pdb', getCommandDescription('pd-bootstrap', language), (ctx) => handleBootstrapTools(ctx));
364
497
  registerCommandWithAlias('pd-research', 'pdr', getCommandDescription('pd-research', language), (ctx) => handleResearchTools(ctx));
365
498
  registerCommandWithAlias('pd-thinking', 'pdt', getCommandDescription('pd-thinking', language), (ctx) => handleThinkingOs(ctx), { acceptsArgs: true });
366
- registerCommandWithAlias('pd-daily', 'pdd', getCommandDescription('pd-daily', language), () => ({
367
- text: language === 'zh'
368
- ? "请执行 pd-daily 技能来配置并发送进化日报。系统将引导你完成配置流程,包括发送时间、渠道和报告风格偏好。"
369
- : "Please execute the pd-daily skill to configure and send your daily evolution report. The system will guide you through the configuration process."
370
- }));
371
- registerCommandWithAlias('pd-grooming', 'pdg', getCommandDescription('pd-grooming', language), () => ({
372
- text: language === 'zh'
373
- ? "请执行 pd-grooming 技能来执行大扫除。例如输入: '执行 pd-grooming 技能'"
374
- : "Please execute the pd-grooming skill to clean up. For example: 'Execute pd-grooming skill'"
375
- }));
376
499
  registerCommandWithAlias('pd-help', 'pdh', getCommandDescription('pd-help', language), () => {
377
500
  if (language === 'zh') {
378
501
  return { text: `
@@ -388,10 +511,7 @@ const plugin = {
388
511
  ## 状态查询
389
512
  | 短命令 | 长命令 | 用途 |
390
513
  |--------|--------|------|
391
- | \`/pdk\` | \`/pd-okr\` | OKR 目标管理 |
392
514
  | \`/pdt\` | \`/pd-thinking\` | 思维模型管理 |
393
- | \`/pdd\` | \`/pd-daily\` | 进化日报 |
394
- | \`/pdg\` | \`/pd-grooming\` | 工作区清理 |
395
515
 
396
516
  ## 其他命令
397
517
  | 命令 | 用途 |
@@ -420,10 +540,7 @@ const plugin = {
420
540
  ## Status
421
541
  | Short | Full | Purpose |
422
542
  |-------|------|---------|
423
- | \`/pdk\` | \`/pd-okr\` | OKR goal management |
424
543
  | \`/pdt\` | \`/pd-thinking\` | Mental model management |
425
- | \`/pdd\` | \`/pd-daily\` | Evolution report |
426
- | \`/pdg\` | \`/pd-grooming\` | Workspace cleanup |
427
544
 
428
545
  ## Other Commands
429
546
  | Command | Purpose |
@@ -685,4 +802,6 @@ const plugin = {
685
802
  }
686
803
  };
687
804
  export { PrincipleTreeLedgerAdapter } from './core/principle-tree-ledger-adapter.js';
805
+ /* istanbul ignore next — test exports for evolution worker gate */
806
+ export { loadFeatureFlagFromWorkspace, isRecord };
688
807
  export default plugin;
@@ -0,0 +1,11 @@
1
+ import type { OpenClawPluginServiceContext, PluginLogger } from '../openclaw-sdk.js';
2
+ import { WorkspaceContext } from '../core/workspace-context.js';
3
+ import { CorrectionObserver } from '@principles/core/runtime-v2';
4
+ export interface CorrectionObserverServiceShape {
5
+ id: string;
6
+ start: (ctx: OpenClawPluginServiceContext) => void;
7
+ stop?: (ctx: OpenClawPluginServiceContext) => void;
8
+ }
9
+ export declare function resolveCorrectionObserver(wctx: WorkspaceContext, logger?: Pick<PluginLogger, 'info' | 'warn' | 'error' | 'debug'>): CorrectionObserver | null;
10
+ export declare function runCorrectionObserverCycle(wctx: WorkspaceContext, logger: PluginLogger): Promise<void>;
11
+ export declare const CorrectionObserverService: CorrectionObserverServiceShape;
@@ -0,0 +1,172 @@
1
+ import { WorkspaceContext } from '../core/workspace-context.js';
2
+ import { TrajectoryRegistry } from '../core/trajectory.js';
3
+ import { CorrectionCueLearner } from '../core/correction-cue-learner.js';
4
+ import { WorkflowFunnelLoader, PiAiRuntimeAdapter, CorrectionObserver, AgentScheduler, } from '@principles/core/runtime-v2';
5
+ import { KeywordOptimizationService } from './keyword-optimization-service.js';
6
+ import { SystemLogger } from '../core/system-logger.js';
7
+ let correctionObserverTimeoutId = null;
8
+ let correctionObserverStopped = false;
9
+ const startedWorkspaces = new Set();
10
+ const CORRECTION_OBSERVER_INTERVAL_MS = 15 * 60 * 1000;
11
+ const CORRECTION_OBSERVER_INITIAL_DELAY_MS = 10_000;
12
+ const CORRECTION_OBSERVER_MAX_RECENT_SESSIONS = 20;
13
+ const CORRECTION_OBSERVER_MAX_PAYLOAD_SESSIONS = 5;
14
+ export function resolveCorrectionObserver(wctx, logger) {
15
+ try {
16
+ const loader = new WorkflowFunnelLoader(wctx.stateDir);
17
+ const funnel = loader.getFunnel('pd-correction-observer');
18
+ const policy = funnel?.policy;
19
+ if (!policy || policy.runtimeKind !== 'pi-ai') {
20
+ logger?.debug?.('[PD:CorrectionObserver] workflows.yaml pd-correction-observer policy not found. Falling back to environment variables.');
21
+ const provider = process.env.PD_CORRECTION_PROVIDER || 'anthropic';
22
+ const model = process.env.PD_CORRECTION_MODEL || 'anthropic/claude-3-5-sonnet';
23
+ const apiKeyEnv = process.env.PD_CORRECTION_API_KEY_ENV || 'ANTHROPIC_API_KEY';
24
+ const baseUrl = process.env.PD_CORRECTION_BASE_URL;
25
+ if (!process.env[apiKeyEnv]) {
26
+ logger?.debug?.(`[PD:CorrectionObserver] API key env ${apiKeyEnv} is not set. Periodic optimization disabled.`);
27
+ return null;
28
+ }
29
+ const adapter = new PiAiRuntimeAdapter({
30
+ provider,
31
+ model,
32
+ apiKeyEnv,
33
+ baseUrl,
34
+ workspace: wctx.workspaceDir,
35
+ });
36
+ return new CorrectionObserver({ runtimeAdapter: adapter });
37
+ }
38
+ const adapter = new PiAiRuntimeAdapter({
39
+ provider: String(policy.provider),
40
+ model: String(policy.model),
41
+ apiKeyEnv: String(policy.apiKeyEnv),
42
+ maxRetries: policy.maxRetries,
43
+ timeoutMs: policy.timeoutMs ?? 30_000,
44
+ baseUrl: policy.baseUrl,
45
+ workspace: wctx.workspaceDir,
46
+ });
47
+ return new CorrectionObserver({ runtimeAdapter: adapter }, { timeoutMs: policy.timeoutMs });
48
+ }
49
+ catch (err) {
50
+ logger?.warn?.(`[PD:CorrectionObserver] Failed to resolve CorrectionObserver: ${String(err)}`);
51
+ return null;
52
+ }
53
+ }
54
+ export async function runCorrectionObserverCycle(wctx, logger) {
55
+ try {
56
+ const observer = resolveCorrectionObserver(wctx, logger);
57
+ if (!observer) {
58
+ logger?.info?.('[PD:CorrectionObserver] Observer not resolved (no API key or config). Skipping cycle.');
59
+ return;
60
+ }
61
+ logger?.info?.('[PD:CorrectionObserver] Observer resolved. Initiating periodic optimization...');
62
+ const db = TrajectoryRegistry.get(wctx.workspaceDir);
63
+ const recentSessions = db.listRecentSessions({ limit: CORRECTION_OBSERVER_MAX_RECENT_SESSIONS });
64
+ const recentSessionIds = recentSessions.map(s => s.sessionId);
65
+ if (recentSessionIds.length === 0) {
66
+ logger?.info?.('[PD:CorrectionObserver] No recent sessions found. Skipping correction optimization.');
67
+ return;
68
+ }
69
+ const recentMessages = [];
70
+ for (const sId of recentSessionIds.slice(0, CORRECTION_OBSERVER_MAX_PAYLOAD_SESSIONS)) {
71
+ try {
72
+ const turns = db.listUserTurnsForSession(sId);
73
+ for (const t of turns) {
74
+ if (t.rawExcerpt) {
75
+ recentMessages.push(t.rawExcerpt);
76
+ }
77
+ }
78
+ }
79
+ catch (turnErr) {
80
+ logger?.warn?.(`[PD:CorrectionObserver] Failed to load user turns for session ${sId}: ${String(turnErr)}`);
81
+ }
82
+ }
83
+ const learner = CorrectionCueLearner.get(wctx.stateDir);
84
+ const keywords = learner.getStore().keywords;
85
+ const keywordStoreSummary = {
86
+ totalKeywords: keywords.length,
87
+ terms: keywords.map(k => ({
88
+ term: k.term,
89
+ weight: k.weight,
90
+ hitCount: k.hitCount ?? 0,
91
+ truePositiveCount: k.truePositiveCount ?? 0,
92
+ falsePositiveCount: k.falsePositiveCount ?? 0,
93
+ })),
94
+ };
95
+ const optimizationService = KeywordOptimizationService.get(wctx.stateDir, wctx.workspaceDir, logger);
96
+ const trajectoryHistory = await optimizationService.buildTrajectoryHistory(recentSessionIds);
97
+ const payload = {
98
+ parentSessionId: 'correction-observer-service',
99
+ workspaceDir: wctx.workspaceDir,
100
+ keywordStoreSummary,
101
+ recentMessages,
102
+ trajectoryHistory,
103
+ };
104
+ const scheduler = new AgentScheduler();
105
+ scheduler.register({
106
+ agentId: 'correction-observer',
107
+ mode: 'realtime',
108
+ runner: observer,
109
+ });
110
+ logger?.info?.(`[PD:CorrectionObserver] Dispatching with ${trajectoryHistory.length} trajectory events, ${recentMessages.length} recent messages.`);
111
+ const result = await scheduler.dispatch('correction-observer', payload);
112
+ logger?.info?.(`[PD:CorrectionObserver] Completed: updated=${result.updated}, summary="${result.summary}"`);
113
+ if (result.updated) {
114
+ optimizationService.applyResult(result);
115
+ }
116
+ }
117
+ catch (err) {
118
+ const errMsg = `Correction observer cycle failed: ${String(err)}`;
119
+ logger?.warn?.(`[PD:CorrectionObserver] ${errMsg}`);
120
+ SystemLogger.log(wctx.workspaceDir, 'CORRECTION_OBSERVER_CYCLE_FAILED', errMsg);
121
+ }
122
+ }
123
+ export const CorrectionObserverService = {
124
+ id: 'principles-correction-observer',
125
+ start(ctx) {
126
+ const workspaceDir = ctx?.workspaceDir;
127
+ const logger = ctx?.logger || console;
128
+ if (!workspaceDir) {
129
+ if (logger)
130
+ logger.warn('[PD:CorrectionObserver] workspaceDir not found in service config. Correction observer disabled.');
131
+ return;
132
+ }
133
+ if (startedWorkspaces.has(workspaceDir)) {
134
+ if (logger)
135
+ logger.info(`[PD:CorrectionObserver] Already started for workspace: ${workspaceDir}. Skipping duplicate start.`);
136
+ return;
137
+ }
138
+ startedWorkspaces.add(workspaceDir);
139
+ correctionObserverStopped = false;
140
+ const wctx = WorkspaceContext.fromHookContext({ workspaceDir, ...ctx.config });
141
+ if (logger)
142
+ logger.info(`[PD:CorrectionObserver] Starting with workspaceDir=${wctx.workspaceDir}, stateDir=${wctx.stateDir}`);
143
+ const interval = CORRECTION_OBSERVER_INTERVAL_MS;
144
+ async function runCycle() {
145
+ if (correctionObserverStopped)
146
+ return;
147
+ await runCorrectionObserverCycle(wctx, logger);
148
+ if (correctionObserverStopped)
149
+ return;
150
+ correctionObserverTimeoutId = setTimeout(runCycle, interval);
151
+ correctionObserverTimeoutId.unref();
152
+ }
153
+ correctionObserverTimeoutId = setTimeout(() => {
154
+ void runCycle().catch((err) => {
155
+ if (logger)
156
+ logger.error(`[PD:CorrectionObserver] Startup cycle failed: ${String(err)}`);
157
+ if (correctionObserverStopped)
158
+ return;
159
+ correctionObserverTimeoutId = setTimeout(runCycle, interval);
160
+ correctionObserverTimeoutId.unref();
161
+ });
162
+ }, CORRECTION_OBSERVER_INITIAL_DELAY_MS);
163
+ correctionObserverTimeoutId.unref();
164
+ },
165
+ stop(_ctx) {
166
+ correctionObserverStopped = true;
167
+ startedWorkspaces.clear();
168
+ if (correctionObserverTimeoutId)
169
+ clearTimeout(correctionObserverTimeoutId);
170
+ correctionObserverTimeoutId = null;
171
+ },
172
+ };