crewly 1.8.13 → 1.10.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.
- package/config/roles/orchestrator/prompt.md +55 -2
- package/config/roles/product-manager/prompt.md +40 -0
- package/config/roles/team-leader/prompt.md +33 -0
- package/config/skills/agent/core/get-sops/SKILL.md +6 -2
- package/config/skills/agent/core/get-sops/execute.sh +26 -1
- package/config/skills/agent/core/get-team-norms/SKILL.md +69 -0
- package/config/skills/agent/core/update-sop/SKILL.md +73 -0
- package/config/skills/agent/core/update-sop/execute.sh +115 -0
- package/config/skills/agent/core/update-team-norm/SKILL.md +67 -0
- package/config/skills/orchestrator/decompose-okr/SKILL.md +107 -0
- package/config/skills/orchestrator/decompose-okr/execute.sh +142 -0
- package/config/skills/registry.json +52 -6
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.d.ts +2 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js +50 -1
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.d.ts +2 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.js +6 -2
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/mission/kr.controller.d.ts +8 -0
- package/dist/backend/backend/src/controllers/mission/kr.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/mission/kr.controller.js +17 -0
- package/dist/backend/backend/src/controllers/mission/kr.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/mission/mission-policy.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/mission/mission-policy.routes.js +198 -26
- package/dist/backend/backend/src/controllers/mission/mission-policy.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.js +10 -0
- package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/system/system.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/system/system.controller.js +2 -1
- package/dist/backend/backend/src/controllers/system/system.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/team/team.controller.d.ts +17 -0
- package/dist/backend/backend/src/controllers/team/team.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/team/team.controller.js +32 -0
- package/dist/backend/backend/src/controllers/team/team.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.d.ts +45 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.js +364 -46
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.js +9 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +44 -70
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +12 -6
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +13 -4
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +47 -11
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.providers.d.ts +28 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.providers.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.providers.js +49 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.providers.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts +60 -2
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js +93 -3
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/legacy-dto.utils.d.ts +25 -0
- package/dist/backend/backend/src/services/chat-v2/legacy-dto.utils.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/legacy-dto.utils.js +33 -0
- package/dist/backend/backend/src/services/chat-v2/legacy-dto.utils.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.d.ts +28 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.js +48 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.d.ts +21 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.js +30 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/types.d.ts +14 -0
- package/dist/backend/backend/src/services/chat-v2/types.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/types.js.map +1 -1
- package/dist/backend/backend/src/services/index.d.ts +0 -1
- package/dist/backend/backend/src/services/index.d.ts.map +1 -1
- package/dist/backend/backend/src/services/index.js +0 -1
- package/dist/backend/backend/src/services/index.js.map +1 -1
- package/dist/backend/backend/src/services/intent-task/intent-task-follow-up.service.d.ts +26 -108
- package/dist/backend/backend/src/services/intent-task/intent-task-follow-up.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/intent-task/intent-task-follow-up.service.js +26 -214
- package/dist/backend/backend/src/services/intent-task/intent-task-follow-up.service.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts +11 -0
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +36 -0
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts +10 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +21 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.d.ts +12 -0
- package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.js +53 -1
- package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/backend/backend/src/services/sop/sop.service.d.ts +10 -0
- package/dist/backend/backend/src/services/sop/sop.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/sop/sop.service.js +71 -10
- package/dist/backend/backend/src/services/sop/sop.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/kr-tracking.service.d.ts +92 -1
- package/dist/backend/backend/src/services/v3/kr-tracking.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/kr-tracking.service.js +169 -1
- package/dist/backend/backend/src/services/v3/kr-tracking.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/mission-period.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/mission-period.service.js +7 -1
- package/dist/backend/backend/src/services/v3/mission-period.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/okr-cascade.service.d.ts +217 -0
- package/dist/backend/backend/src/services/v3/okr-cascade.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/v3/okr-cascade.service.js +417 -0
- package/dist/backend/backend/src/services/v3/okr-cascade.service.js.map +1 -0
- package/dist/backend/backend/src/services/v3/okr-review.service.d.ts +11 -0
- package/dist/backend/backend/src/services/v3/okr-review.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/okr-review.service.js +50 -0
- package/dist/backend/backend/src/services/v3/okr-review.service.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/sop-catalog.service.d.ts +83 -0
- package/dist/backend/backend/src/services/wiki/sop-catalog.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/sop-catalog.service.js +185 -0
- package/dist/backend/backend/src/services/wiki/sop-catalog.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-cleanup.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-cleanup.service.js +12 -0
- package/dist/backend/backend/src/services/wiki/wiki-cleanup.service.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-migrate.service.d.ts +91 -0
- package/dist/backend/backend/src/services/wiki/wiki-migrate.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-migrate.service.js +180 -0
- package/dist/backend/backend/src/services/wiki/wiki-migrate.service.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-overlay.resolver.d.ts +43 -0
- package/dist/backend/backend/src/services/wiki/wiki-overlay.resolver.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-overlay.resolver.js +67 -0
- package/dist/backend/backend/src/services/wiki/wiki-overlay.resolver.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-search.service.d.ts +79 -19
- package/dist/backend/backend/src/services/wiki/wiki-search.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-search.service.js +275 -106
- package/dist/backend/backend/src/services/wiki/wiki-search.service.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.js +13 -0
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.js.map +1 -1
- package/dist/backend/backend/src/services/workflow/cron-task.service.d.ts +13 -0
- package/dist/backend/backend/src/services/workflow/cron-task.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/workflow/cron-task.service.js +43 -3
- package/dist/backend/backend/src/services/workflow/cron-task.service.js.map +1 -1
- package/dist/backend/backend/src/types/sop.types.d.ts +7 -0
- package/dist/backend/backend/src/types/sop.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/sop.types.js.map +1 -1
- package/dist/backend/backend/src/types/v2/index.d.ts +4 -4
- package/dist/backend/backend/src/types/v2/index.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/index.js +2 -2
- package/dist/backend/backend/src/types/v2/index.js.map +1 -1
- package/dist/backend/backend/src/types/v2/key-result.types.d.ts +84 -0
- package/dist/backend/backend/src/types/v2/key-result.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/key-result.types.js +73 -3
- package/dist/backend/backend/src/types/v2/key-result.types.js.map +1 -1
- package/dist/backend/backend/src/types/v2/mission.types.d.ts +235 -0
- package/dist/backend/backend/src/types/v2/mission.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/mission.types.js +230 -0
- package/dist/backend/backend/src/types/v2/mission.types.js.map +1 -1
- package/dist/backend/backend/src/types/v2/work-item.types.d.ts +7 -0
- package/dist/backend/backend/src/types/v2/work-item.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/work-item.types.js +1 -1
- package/dist/backend/backend/src/types/v2/work-item.types.js.map +1 -1
- package/dist/cli/backend/src/types/sop.types.d.ts +7 -0
- package/dist/cli/backend/src/types/sop.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/sop.types.js.map +1 -1
- package/dist/cli/backend/src/types/v2/work-item.types.d.ts +7 -0
- package/dist/cli/backend/src/types/v2/work-item.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/v2/work-item.types.js +1 -1
- package/dist/cli/backend/src/types/v2/work-item.types.js.map +1 -1
- package/frontend/dist/assets/index-4099a91c.js +4961 -0
- package/frontend/dist/assets/index-44266b5d.css +42 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-068bb4f6.css +0 -42
- package/frontend/dist/assets/index-c24ceb15.js +0 -4960
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OKR Cascade Service
|
|
3
|
+
*
|
|
4
|
+
* Owns the decompose → propose → approve/reject workflow for the OKR cascade
|
|
5
|
+
* (company → team → project). A team-leader/PM agent drafts child OKRs as a
|
|
6
|
+
* PROPOSAL; the child Missions are persisted with
|
|
7
|
+
* `approval.state = 'pending_approval'` and are NOT cascade-active until the
|
|
8
|
+
* human owner approves. Rejection carries a required reason.
|
|
9
|
+
*
|
|
10
|
+
* This service is the SINGLE owner of `approval.state` transitions — the
|
|
11
|
+
* `updateMission` PUT route blocks direct `approval` writes so that all
|
|
12
|
+
* transitions flow through {@link OKRCascadeService.approveDecomposition} and
|
|
13
|
+
* {@link OKRCascadeService.rejectDecomposition} (spec §3.2/§3.3).
|
|
14
|
+
*
|
|
15
|
+
* FLAG (spec §2.5): the approval *workflow* (this service + its routes) is a
|
|
16
|
+
* plausible future move to `crewly-pro` as a gated governance feature. The
|
|
17
|
+
* approval *types* live in `mission.types.ts` and stay there; only this
|
|
18
|
+
* workflow layer would relocate. Do NOT split now.
|
|
19
|
+
*
|
|
20
|
+
* Persistence reuses the existing on-disk layout:
|
|
21
|
+
* - Missions: `.crewly/missions/{id}.json`
|
|
22
|
+
* - KRs: `.crewly/missions/{id}/key-results/` (via {@link KRTrackingService})
|
|
23
|
+
*
|
|
24
|
+
* @module services/v3/okr-cascade.service
|
|
25
|
+
*/
|
|
26
|
+
import * as fs from 'fs/promises';
|
|
27
|
+
import * as path from 'path';
|
|
28
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
29
|
+
import { KRTrackingService } from './kr-tracking.service.js';
|
|
30
|
+
import { createMission, validateCascadeLink, resolveMissionLevel, isMission, isValidProposalState, isValidProposalTransition, MISSION_LEVELS, MISSION_LEVEL_DEPTH, } from '../../types/v2/mission.types.js';
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Constants
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
/** Workspace root folder for Crewly runtime state. */
|
|
35
|
+
const CREWLY_HOME = '.crewly';
|
|
36
|
+
/** Folder under {@link CREWLY_HOME} that holds one JSON file per Mission. */
|
|
37
|
+
const MISSIONS_DIR = 'missions';
|
|
38
|
+
/** Proposal state stamped on a freshly-proposed child mission. */
|
|
39
|
+
const PROPOSED_STATE = 'pending_approval';
|
|
40
|
+
/** Proposal state after a successful owner approval. */
|
|
41
|
+
const APPROVED_STATE = 'approved';
|
|
42
|
+
/** Proposal state after an owner rejection. */
|
|
43
|
+
const REJECTED_STATE = 'rejected';
|
|
44
|
+
/**
|
|
45
|
+
* State a corrupt/unrecognized persisted `approval.state` is normalized to on
|
|
46
|
+
* read. `rejected` is a recognized, INACTIVE, non-approvable state: a corrupt
|
|
47
|
+
* doc must NOT be trusted as cascade-active and must NOT be approvable directly
|
|
48
|
+
* (the only approve path is `pending_approval → approved`). See
|
|
49
|
+
* {@link OKRCascadeService.sanitizeMission}.
|
|
50
|
+
*/
|
|
51
|
+
const CORRUPT_APPROVAL_FALLBACK = REJECTED_STATE;
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Service
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
/**
|
|
56
|
+
* Singleton service implementing the OKR decomposition / approval workflow.
|
|
57
|
+
*/
|
|
58
|
+
export class OKRCascadeService {
|
|
59
|
+
static instance = null;
|
|
60
|
+
logger;
|
|
61
|
+
constructor() {
|
|
62
|
+
this.logger = LoggerService.getInstance().createComponentLogger('OKRCascade');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get the singleton instance.
|
|
66
|
+
*
|
|
67
|
+
* @returns The shared {@link OKRCascadeService}
|
|
68
|
+
*/
|
|
69
|
+
static getInstance() {
|
|
70
|
+
if (!OKRCascadeService.instance) {
|
|
71
|
+
OKRCascadeService.instance = new OKRCascadeService();
|
|
72
|
+
}
|
|
73
|
+
return OKRCascadeService.instance;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Reset the singleton (for testing).
|
|
77
|
+
*/
|
|
78
|
+
static resetInstance() {
|
|
79
|
+
OKRCascadeService.instance = null;
|
|
80
|
+
}
|
|
81
|
+
// -------------------------------------------------------------------------
|
|
82
|
+
// Public API
|
|
83
|
+
// -------------------------------------------------------------------------
|
|
84
|
+
/**
|
|
85
|
+
* Draft a child OKR decomposition for a parent mission as a PROPOSAL.
|
|
86
|
+
*
|
|
87
|
+
* The parent must exist and be cascade-active (approval absent or
|
|
88
|
+
* `approval.state === 'approved'`). The child level is computed as exactly one
|
|
89
|
+
* tier below the parent; decomposing a `project`-level parent is rejected
|
|
90
|
+
* (there is no tier below project). Each child mission is created with
|
|
91
|
+
* `approval.state = 'pending_approval'` and is NOT active until the owner
|
|
92
|
+
* approves. Each child's Key Results are created via {@link KRTrackingService}
|
|
93
|
+
* (no duplicate KR persistence here).
|
|
94
|
+
*
|
|
95
|
+
* The decomposition is "fail before writing": EVERY child is fully validated
|
|
96
|
+
* (project-id presence + cascade-link adjacency/cycle) BEFORE any mission or
|
|
97
|
+
* KR is persisted, so a later invalid child never leaves an earlier child
|
|
98
|
+
* half-written to disk.
|
|
99
|
+
*
|
|
100
|
+
* @param parentId - ID of the parent mission to decompose
|
|
101
|
+
* @param input - The proposed child OKRs (objectives + KRs)
|
|
102
|
+
* @param proposedBy - Agent session that drafted the proposal
|
|
103
|
+
* @returns The parent id, resolved child level, and created child IDs
|
|
104
|
+
* @throws If the parent is missing, not approved, already at `project` level,
|
|
105
|
+
* the input has no children, or a child fails cascade-link validation
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* const result = await OKRCascadeService.getInstance().proposeDecomposition(
|
|
110
|
+
* 'co-1',
|
|
111
|
+
* { children: [{ objective: 'Ship v2', currentStrategy: '...', successCriteria: [], keyResults: [] }] },
|
|
112
|
+
* 'team-leader-session',
|
|
113
|
+
* );
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
async proposeDecomposition(parentId, input, proposedBy) {
|
|
117
|
+
if (!input.children || input.children.length === 0) {
|
|
118
|
+
throw new Error('Decomposition requires at least one child OKR');
|
|
119
|
+
}
|
|
120
|
+
const all = await this.loadAllMissions();
|
|
121
|
+
const byId = new Map(all.map((m) => [m.id, m]));
|
|
122
|
+
const parent = byId.get(parentId);
|
|
123
|
+
if (!parent) {
|
|
124
|
+
throw new Error(`Parent mission ${parentId} does not exist`);
|
|
125
|
+
}
|
|
126
|
+
if (!this.isCascadeActive(parent)) {
|
|
127
|
+
throw new Error(`Parent mission ${parentId} is not approved; only approved missions can be decomposed`);
|
|
128
|
+
}
|
|
129
|
+
const parentLevel = this.resolveLevel(parent, byId);
|
|
130
|
+
const childLevel = this.nextLevel(parentLevel);
|
|
131
|
+
if (childLevel === null) {
|
|
132
|
+
throw new Error(`A ${parentLevel}-level mission is the bottom of the cascade and cannot be decomposed further`);
|
|
133
|
+
}
|
|
134
|
+
// ----- Pre-write validation pass (fail BEFORE any persist) -------------
|
|
135
|
+
// Build every child mission up-front and run the FULL cascade guard
|
|
136
|
+
// (project-id presence + validateCascadeLink) against the live tree plus
|
|
137
|
+
// the siblings already built in this same proposal. Only once ALL children
|
|
138
|
+
// pass do we persist anything — so a later invalid child can never leave an
|
|
139
|
+
// earlier child (or its KRs) on disk (transactional "all-or-nothing").
|
|
140
|
+
const now = new Date().toISOString();
|
|
141
|
+
const linkById = new Map(all.map((m) => [m.id, { id: m.id, parentMissionId: m.parentMissionId, level: m.level }]));
|
|
142
|
+
const prepared = [];
|
|
143
|
+
for (const child of input.children) {
|
|
144
|
+
if (childLevel === 'project' && !child.projectId) {
|
|
145
|
+
throw new Error('A project-level child OKR requires projectId');
|
|
146
|
+
}
|
|
147
|
+
const mission = createMission({
|
|
148
|
+
objective: child.objective,
|
|
149
|
+
ownerTeamId: parent.ownerTeamId,
|
|
150
|
+
successCriteria: child.successCriteria,
|
|
151
|
+
currentStrategy: child.currentStrategy,
|
|
152
|
+
parentMissionId: parentId,
|
|
153
|
+
level: childLevel,
|
|
154
|
+
projectId: child.projectId,
|
|
155
|
+
approval: {
|
|
156
|
+
state: PROPOSED_STATE,
|
|
157
|
+
proposedBy,
|
|
158
|
+
proposedAt: now,
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
const reason = validateCascadeLink(mission.id, childLevel, parentId, linkById);
|
|
162
|
+
if (reason) {
|
|
163
|
+
throw new Error(`Invalid cascade link for child OKR "${child.objective}": ${reason}`);
|
|
164
|
+
}
|
|
165
|
+
// Register the just-validated child so subsequent siblings see it.
|
|
166
|
+
linkById.set(mission.id, {
|
|
167
|
+
id: mission.id,
|
|
168
|
+
parentMissionId: mission.parentMissionId,
|
|
169
|
+
level: mission.level,
|
|
170
|
+
});
|
|
171
|
+
prepared.push({ mission, child });
|
|
172
|
+
}
|
|
173
|
+
// ----- Write pass (only reached when ALL children validated) -----------
|
|
174
|
+
const childMissionIds = [];
|
|
175
|
+
const krService = KRTrackingService.getInstance();
|
|
176
|
+
for (const { mission, child } of prepared) {
|
|
177
|
+
await this.persistMission(mission);
|
|
178
|
+
// KRs created via KRTrackingService — single source of KR persistence.
|
|
179
|
+
for (const kr of child.keyResults) {
|
|
180
|
+
await krService.create({ ...kr, missionId: mission.id });
|
|
181
|
+
}
|
|
182
|
+
childMissionIds.push(mission.id);
|
|
183
|
+
}
|
|
184
|
+
this.logger.info('Proposed OKR decomposition', {
|
|
185
|
+
parentId,
|
|
186
|
+
childLevel,
|
|
187
|
+
childCount: childMissionIds.length,
|
|
188
|
+
proposedBy,
|
|
189
|
+
});
|
|
190
|
+
return { parentId, childLevel, childMissionIds };
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Approve a pending decomposition proposal, making the child mission
|
|
194
|
+
* cascade-active. Guarded by {@link isValidProposalTransition} so only a
|
|
195
|
+
* `pending_approval` mission may transition to `approved`.
|
|
196
|
+
*
|
|
197
|
+
* @param missionId - ID of the proposed (pending_approval) mission
|
|
198
|
+
* @param decidedBy - Human owner who approved (e.g. "steve")
|
|
199
|
+
* @returns The updated mission
|
|
200
|
+
* @throws If the mission is missing, has no approval state, or the
|
|
201
|
+
* `pending_approval → approved` transition is illegal
|
|
202
|
+
*/
|
|
203
|
+
async approveDecomposition(missionId, decidedBy) {
|
|
204
|
+
const mission = await this.loadMission(missionId);
|
|
205
|
+
if (!mission) {
|
|
206
|
+
throw new Error(`Mission ${missionId} does not exist`);
|
|
207
|
+
}
|
|
208
|
+
const current = mission.approval?.state;
|
|
209
|
+
if (!current) {
|
|
210
|
+
throw new Error(`Mission ${missionId} has no proposal to approve`);
|
|
211
|
+
}
|
|
212
|
+
if (!isValidProposalTransition(current, APPROVED_STATE)) {
|
|
213
|
+
throw new Error(`Cannot approve a mission in state "${current}"`);
|
|
214
|
+
}
|
|
215
|
+
const decided = {
|
|
216
|
+
...mission,
|
|
217
|
+
approval: {
|
|
218
|
+
...mission.approval,
|
|
219
|
+
state: APPROVED_STATE,
|
|
220
|
+
decidedBy,
|
|
221
|
+
decidedAt: new Date().toISOString(),
|
|
222
|
+
},
|
|
223
|
+
updatedAt: new Date().toISOString(),
|
|
224
|
+
};
|
|
225
|
+
await this.persistMission(decided);
|
|
226
|
+
this.logger.info('Approved OKR decomposition', { missionId, decidedBy });
|
|
227
|
+
return decided;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Reject a pending decomposition proposal with a required reason. The
|
|
231
|
+
* rejected mission is excluded from roll-up and autonomous execution.
|
|
232
|
+
*
|
|
233
|
+
* @param missionId - ID of the proposed (pending_approval) mission
|
|
234
|
+
* @param decidedBy - Human owner who rejected (e.g. "steve")
|
|
235
|
+
* @param reason - Non-empty explanation surfaced back to the proposing agent
|
|
236
|
+
* @returns The updated mission
|
|
237
|
+
* @throws If the reason is empty, the mission is missing, has no approval
|
|
238
|
+
* state, or the `pending_approval → rejected` transition is illegal
|
|
239
|
+
*/
|
|
240
|
+
async rejectDecomposition(missionId, decidedBy, reason) {
|
|
241
|
+
if (!reason || reason.trim().length === 0) {
|
|
242
|
+
throw new Error('Rejection requires a non-empty reason');
|
|
243
|
+
}
|
|
244
|
+
const mission = await this.loadMission(missionId);
|
|
245
|
+
if (!mission) {
|
|
246
|
+
throw new Error(`Mission ${missionId} does not exist`);
|
|
247
|
+
}
|
|
248
|
+
const current = mission.approval?.state;
|
|
249
|
+
if (!current) {
|
|
250
|
+
throw new Error(`Mission ${missionId} has no proposal to reject`);
|
|
251
|
+
}
|
|
252
|
+
if (!isValidProposalTransition(current, REJECTED_STATE)) {
|
|
253
|
+
throw new Error(`Cannot reject a mission in state "${current}"`);
|
|
254
|
+
}
|
|
255
|
+
const decided = {
|
|
256
|
+
...mission,
|
|
257
|
+
approval: {
|
|
258
|
+
...mission.approval,
|
|
259
|
+
state: REJECTED_STATE,
|
|
260
|
+
decidedBy,
|
|
261
|
+
decidedAt: new Date().toISOString(),
|
|
262
|
+
rejectionReason: reason.trim(),
|
|
263
|
+
},
|
|
264
|
+
updatedAt: new Date().toISOString(),
|
|
265
|
+
};
|
|
266
|
+
await this.persistMission(decided);
|
|
267
|
+
this.logger.info('Rejected OKR decomposition', { missionId, decidedBy, reason: reason.trim() });
|
|
268
|
+
return decided;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* List missions awaiting an owner decision (`approval.state ===
|
|
272
|
+
* 'pending_approval'`). Optionally scope to the direct children of one parent.
|
|
273
|
+
*
|
|
274
|
+
* @param parentId - When provided, only pending children of this parent
|
|
275
|
+
* @returns Pending-approval missions
|
|
276
|
+
*/
|
|
277
|
+
async listPendingApprovals(parentId) {
|
|
278
|
+
const all = await this.loadAllMissions();
|
|
279
|
+
return all.filter((m) => {
|
|
280
|
+
if (m.approval?.state !== PROPOSED_STATE)
|
|
281
|
+
return false;
|
|
282
|
+
if (parentId !== undefined && m.parentMissionId !== parentId)
|
|
283
|
+
return false;
|
|
284
|
+
return true;
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
// -------------------------------------------------------------------------
|
|
288
|
+
// Internal helpers
|
|
289
|
+
// -------------------------------------------------------------------------
|
|
290
|
+
/**
|
|
291
|
+
* Whether a mission counts as cascade-active: approval absent (legacy) or
|
|
292
|
+
* explicitly approved.
|
|
293
|
+
*
|
|
294
|
+
* @param mission - Mission to test
|
|
295
|
+
* @returns `true` if the mission participates in the cascade
|
|
296
|
+
*/
|
|
297
|
+
isCascadeActive(mission) {
|
|
298
|
+
return mission.approval === undefined || mission.approval.state === APPROVED_STATE;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Resolve a mission's effective level through the SINGLE canonical resolver
|
|
302
|
+
* shared with the routes' read-migration ({@link resolveMissionLevel}), which
|
|
303
|
+
* prefers an explicit `level` and otherwise falls back to `deriveLevel` over
|
|
304
|
+
* the parent chain. A parentless legacy mission therefore resolves to
|
|
305
|
+
* `company` consistently across every read path.
|
|
306
|
+
*
|
|
307
|
+
* @param mission - Mission whose level to resolve
|
|
308
|
+
* @param byId - All missions indexed by ID for the parent-chain walk
|
|
309
|
+
* @returns The mission's resolved level
|
|
310
|
+
*/
|
|
311
|
+
resolveLevel(mission, byId) {
|
|
312
|
+
return resolveMissionLevel(mission, byId);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Compute the cascade level exactly one tier below the given level.
|
|
316
|
+
*
|
|
317
|
+
* @param level - Parent level
|
|
318
|
+
* @returns The child level, or `null` if `level` is already the bottom tier
|
|
319
|
+
*/
|
|
320
|
+
nextLevel(level) {
|
|
321
|
+
const childDepth = MISSION_LEVEL_DEPTH[level] + 1;
|
|
322
|
+
return MISSION_LEVELS[childDepth] ?? null;
|
|
323
|
+
}
|
|
324
|
+
/** Absolute path to the missions directory under the current cwd. */
|
|
325
|
+
getMissionsDir() {
|
|
326
|
+
return path.join(process.cwd(), CREWLY_HOME, MISSIONS_DIR);
|
|
327
|
+
}
|
|
328
|
+
/** Absolute path to a single mission's JSON file. */
|
|
329
|
+
getMissionPath(missionId) {
|
|
330
|
+
return path.join(this.getMissionsDir(), `${missionId}.json`);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Validate + normalize a parsed mission document read from disk.
|
|
334
|
+
*
|
|
335
|
+
* - Returns `null` for anything that is not structurally a {@link Mission}
|
|
336
|
+
* (corrupt / hand-edited / wrong shape), so callers never trust junk.
|
|
337
|
+
* - When `approval` is present but `approval.state` is NOT a recognized
|
|
338
|
+
* {@link isValidProposalState}, the state is normalized to
|
|
339
|
+
* {@link CORRUPT_APPROVAL_FALLBACK} (`rejected`). This deliberately treats a
|
|
340
|
+
* corrupt governance state as INACTIVE — neither cascade-active nor a
|
|
341
|
+
* recognized pending state — rather than silently trusting an unknown
|
|
342
|
+
* string (which would otherwise be neither active nor pending and could
|
|
343
|
+
* crash transition guards).
|
|
344
|
+
*
|
|
345
|
+
* @param value - Raw JSON-parsed value
|
|
346
|
+
* @returns A validated, normalized Mission, or `null` if not a valid Mission
|
|
347
|
+
*/
|
|
348
|
+
sanitizeMission(value) {
|
|
349
|
+
if (!isMission(value))
|
|
350
|
+
return null;
|
|
351
|
+
const mission = value;
|
|
352
|
+
if (mission.approval !== undefined && !isValidProposalState(mission.approval.state)) {
|
|
353
|
+
this.logger.warn('Normalized corrupt approval.state to inactive', {
|
|
354
|
+
missionId: mission.id,
|
|
355
|
+
rawState: mission.approval.state,
|
|
356
|
+
});
|
|
357
|
+
return {
|
|
358
|
+
...mission,
|
|
359
|
+
approval: { ...mission.approval, state: CORRUPT_APPROVAL_FALLBACK },
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
return mission;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Load a single mission from disk, validated + normalized via
|
|
366
|
+
* {@link sanitizeMission}.
|
|
367
|
+
*
|
|
368
|
+
* @param missionId - Mission ID
|
|
369
|
+
* @returns The mission, or `null` if missing/unreadable/not a valid Mission
|
|
370
|
+
*/
|
|
371
|
+
async loadMission(missionId) {
|
|
372
|
+
try {
|
|
373
|
+
const raw = await fs.readFile(this.getMissionPath(missionId), 'utf-8');
|
|
374
|
+
return this.sanitizeMission(JSON.parse(raw));
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Load all missions from disk, skipping unreadable AND structurally-invalid
|
|
382
|
+
* files (each validated + normalized via {@link sanitizeMission}).
|
|
383
|
+
*
|
|
384
|
+
* @returns Array of valid missions
|
|
385
|
+
*/
|
|
386
|
+
async loadAllMissions() {
|
|
387
|
+
const dir = this.getMissionsDir();
|
|
388
|
+
let files = [];
|
|
389
|
+
try {
|
|
390
|
+
files = (await fs.readdir(dir)).filter((f) => f.endsWith('.json'));
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
return [];
|
|
394
|
+
}
|
|
395
|
+
const missions = await Promise.all(files.map(async (f) => {
|
|
396
|
+
try {
|
|
397
|
+
const raw = await fs.readFile(path.join(dir, f), 'utf-8');
|
|
398
|
+
return this.sanitizeMission(JSON.parse(raw));
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
}));
|
|
404
|
+
return missions.filter((m) => m !== null);
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Persist a mission to disk (creating the missions directory if needed).
|
|
408
|
+
*
|
|
409
|
+
* @param mission - Mission to write
|
|
410
|
+
*/
|
|
411
|
+
async persistMission(mission) {
|
|
412
|
+
const dir = this.getMissionsDir();
|
|
413
|
+
await fs.mkdir(dir, { recursive: true });
|
|
414
|
+
await fs.writeFile(this.getMissionPath(mission.id), JSON.stringify(mission, null, 2));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
//# sourceMappingURL=okr-cascade.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"okr-cascade.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/v3/okr-cascade.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -50,6 +50,17 @@ export declare class OKRReviewService {
|
|
|
50
50
|
* Update mission fields on disk (partial merge).
|
|
51
51
|
*/
|
|
52
52
|
private updateMissionReview;
|
|
53
|
+
/**
|
|
54
|
+
* Refresh a parent mission's `lastReviewSummary` using the cascade roll-up.
|
|
55
|
+
*
|
|
56
|
+
* Called on-demand after a child review so the parent reflects its children's
|
|
57
|
+
* latest progress without a separate scheduled review. No-op when the
|
|
58
|
+
* reviewed mission has no parent or the parent has vanished. Errors are
|
|
59
|
+
* swallowed and logged — a roll-up refresh must never fail the child review.
|
|
60
|
+
*
|
|
61
|
+
* @param parentMissionId - The reviewed mission's parent (may be undefined)
|
|
62
|
+
*/
|
|
63
|
+
private refreshParentRollup;
|
|
53
64
|
/**
|
|
54
65
|
* Extract previous overall progress from review summary string.
|
|
55
66
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"okr-review.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/v3/okr-review.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAOH,OAAO,KAAK,EACV,eAAe,EACf,cAAc,
|
|
1
|
+
{"version":3,"file":"okr-review.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/v3/okr-review.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAOH,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EAGf,MAAM,oCAAoC,CAAC;AA6B5C;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAiC;IACxD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IAEzC,OAAO;IAIP,MAAM,CAAC,WAAW,IAAI,gBAAgB;IAOtC,MAAM,CAAC,aAAa,IAAI,IAAI;IAQ5B;;;;;;;;;;;;OAYG;IACG,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IA8EhE;;;;;OAKG;IACG,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAmEvF;;OAEG;YACW,WAAW;IAUzB;;OAEG;YACW,mBAAmB;IAYjC;;;;;;;;;OASG;YACW,mBAAmB;IAwBjC;;OAEG;IACH,OAAO,CAAC,uBAAuB;CAKhC"}
|
|
@@ -22,6 +22,16 @@ import { MissionExecutorService } from './mission-executor.service.js';
|
|
|
22
22
|
function getMissionsDir() {
|
|
23
23
|
return path.join(process.cwd(), '.crewly', 'missions');
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Build the one-line review summary string persisted on a mission. Kept in one
|
|
27
|
+
* place so the on-demand parent roll-up refresh and the self-review write stay
|
|
28
|
+
* consistent and machine-parseable (see {@link OKRReviewService}).
|
|
29
|
+
*/
|
|
30
|
+
function formatCascadeSummary(summary) {
|
|
31
|
+
return (`Roll-up: ${summary.rolledUpProgress}% ` +
|
|
32
|
+
`(own ${summary.overallProgress}%, ${summary.childMissionCount} child OKR(s)) | ` +
|
|
33
|
+
`Recommendation: ${summary.recommendation}`);
|
|
34
|
+
}
|
|
25
35
|
// ---------------------------------------------------------------------------
|
|
26
36
|
// Service
|
|
27
37
|
// ---------------------------------------------------------------------------
|
|
@@ -109,6 +119,10 @@ export class OKRReviewService {
|
|
|
109
119
|
staleCycles: newStaleCycles,
|
|
110
120
|
lastReviewAt: result.reviewedAt,
|
|
111
121
|
});
|
|
122
|
+
// Cross-level roll-up: on-demand at review time, refresh this mission's
|
|
123
|
+
// parent so the parent's lastReviewSummary reflects the (now-updated) child.
|
|
124
|
+
// Event-driven refresh on every child measurement is a follow-up (spec §4.3).
|
|
125
|
+
await this.refreshParentRollup(mission.parentMissionId);
|
|
112
126
|
this.logger.info('OKR review completed', {
|
|
113
127
|
missionId,
|
|
114
128
|
progress: finalSummary.overallProgress,
|
|
@@ -208,6 +222,42 @@ export class OKRReviewService {
|
|
|
208
222
|
const filePath = path.join(getMissionsDir(), `${missionId}.json`);
|
|
209
223
|
await fs.writeFile(filePath, JSON.stringify(merged, null, 2));
|
|
210
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Refresh a parent mission's `lastReviewSummary` using the cascade roll-up.
|
|
227
|
+
*
|
|
228
|
+
* Called on-demand after a child review so the parent reflects its children's
|
|
229
|
+
* latest progress without a separate scheduled review. No-op when the
|
|
230
|
+
* reviewed mission has no parent or the parent has vanished. Errors are
|
|
231
|
+
* swallowed and logged — a roll-up refresh must never fail the child review.
|
|
232
|
+
*
|
|
233
|
+
* @param parentMissionId - The reviewed mission's parent (may be undefined)
|
|
234
|
+
*/
|
|
235
|
+
async refreshParentRollup(parentMissionId) {
|
|
236
|
+
if (!parentMissionId)
|
|
237
|
+
return;
|
|
238
|
+
const parent = await this.loadMission(parentMissionId);
|
|
239
|
+
if (!parent)
|
|
240
|
+
return;
|
|
241
|
+
try {
|
|
242
|
+
const krService = KRTrackingService.getInstance();
|
|
243
|
+
const rollup = await krService.computeCascadeOKRProgress(parentMissionId);
|
|
244
|
+
await this.updateMissionReview(parentMissionId, {
|
|
245
|
+
lastReviewSummary: formatCascadeSummary(rollup),
|
|
246
|
+
lastReviewAt: new Date().toISOString(),
|
|
247
|
+
});
|
|
248
|
+
this.logger.info('Refreshed parent roll-up summary', {
|
|
249
|
+
parentMissionId,
|
|
250
|
+
rolledUpProgress: rollup.rolledUpProgress,
|
|
251
|
+
childMissionCount: rollup.childMissionCount,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
this.logger.warn('Parent roll-up refresh failed', {
|
|
256
|
+
parentMissionId,
|
|
257
|
+
error: err.message,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
211
261
|
/**
|
|
212
262
|
* Extract previous overall progress from review summary string.
|
|
213
263
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"okr-review.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/v3/okr-review.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAwB,MAAM,2BAA2B,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"okr-review.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/v3/okr-review.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SOP catalog service.
|
|
3
|
+
*
|
|
4
|
+
* `config/sops/` is a *catalog* of reusable SOPs (a marketplace), NOT content a
|
|
5
|
+
* team owns automatically. A team owns a SOP only once it is INSTALLED — copied
|
|
6
|
+
* into the team's own `~/.crewly/teams/<id>/sops/` store, which the wiki then
|
|
7
|
+
* surfaces (via the overlay resolver) under the team's `sop/` folder.
|
|
8
|
+
*
|
|
9
|
+
* This service lists the catalog, reports which entries a given team has
|
|
10
|
+
* installed, and performs install / uninstall (copy / remove of the per-team
|
|
11
|
+
* copy — the catalog original is never touched).
|
|
12
|
+
*
|
|
13
|
+
* @module services/wiki/sop-catalog.service
|
|
14
|
+
*/
|
|
15
|
+
/** A single SOP available in the catalog. */
|
|
16
|
+
export interface SopCatalogEntry {
|
|
17
|
+
/** Catalog-relative path, e.g. `pm/progress-tracking.md`. Stable id. */
|
|
18
|
+
path: string;
|
|
19
|
+
/** Display title (from frontmatter `title:` or the filename). */
|
|
20
|
+
title: string;
|
|
21
|
+
/** Top-level category folder (`common`, `developer`, …) or `general`. */
|
|
22
|
+
category: string;
|
|
23
|
+
/** File size in bytes. */
|
|
24
|
+
bytes: number;
|
|
25
|
+
/** Whether the queried team has this SOP installed. */
|
|
26
|
+
installed: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the catalog directory (`<configDir>/sops`). `<configDir>` mirrors the
|
|
30
|
+
* convention used by other services (`<cwd>/config`), overridable via
|
|
31
|
+
* `CREWLY_CONFIG_DIR`.
|
|
32
|
+
*
|
|
33
|
+
* @returns Absolute path to the SOP catalog directory.
|
|
34
|
+
*/
|
|
35
|
+
export declare function sopCatalogDir(): string;
|
|
36
|
+
/**
|
|
37
|
+
* Resolve a team's installed-SOP store from its vault path. The store is a
|
|
38
|
+
* sibling of the vault (`<vault>/../sops`), matching the overlay resolver.
|
|
39
|
+
*
|
|
40
|
+
* @param vaultPath - Absolute path to the team vault (`.../teams/<id>/wiki`).
|
|
41
|
+
* @returns Absolute path to the team's installed-SOP directory.
|
|
42
|
+
*/
|
|
43
|
+
export declare function teamSopsDir(vaultPath: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* The SOP catalog service. Stateless; methods take the team vault path so the
|
|
46
|
+
* same instance serves any team.
|
|
47
|
+
*/
|
|
48
|
+
export declare class SopCatalogService {
|
|
49
|
+
/**
|
|
50
|
+
* List the catalog, annotated with whether the given team has each installed.
|
|
51
|
+
*
|
|
52
|
+
* @param vaultPath - The team vault path (to resolve its installed store).
|
|
53
|
+
* @returns Catalog entries sorted by category then path.
|
|
54
|
+
*/
|
|
55
|
+
list(vaultPath: string): Promise<SopCatalogEntry[]>;
|
|
56
|
+
/**
|
|
57
|
+
* Install a catalog SOP into the team's store (copy, preserving the relative
|
|
58
|
+
* path). Idempotent — re-installing refreshes the copy to the catalog version.
|
|
59
|
+
*
|
|
60
|
+
* @param vaultPath - The team vault path.
|
|
61
|
+
* @param sopPath - Catalog-relative SOP path (e.g. `pm/progress-tracking.md`).
|
|
62
|
+
* @returns `{ installed: true, path }`.
|
|
63
|
+
* @throws Error when the path is unsafe or the catalog file is missing.
|
|
64
|
+
*/
|
|
65
|
+
install(vaultPath: string, sopPath: string): Promise<{
|
|
66
|
+
installed: true;
|
|
67
|
+
path: string;
|
|
68
|
+
}>;
|
|
69
|
+
/**
|
|
70
|
+
* Remove a SOP from the team's store. The catalog original is untouched.
|
|
71
|
+
* Idempotent — removing a not-installed SOP is a no-op.
|
|
72
|
+
*
|
|
73
|
+
* @param vaultPath - The team vault path.
|
|
74
|
+
* @param sopPath - Catalog-relative SOP path.
|
|
75
|
+
* @returns `{ installed: false, path }`.
|
|
76
|
+
* @throws Error when the path is unsafe.
|
|
77
|
+
*/
|
|
78
|
+
uninstall(vaultPath: string, sopPath: string): Promise<{
|
|
79
|
+
installed: false;
|
|
80
|
+
path: string;
|
|
81
|
+
}>;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=sop-catalog.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sop-catalog.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/wiki/sop-catalog.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,6CAA6C;AAC7C,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAC;IACjB,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAKtC;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAErD;AA0DD;;;GAGG;AACH,qBAAa,iBAAiB;IAC5B;;;;;OAKG;IACG,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA4BzD;;;;;;;;OAQG;IACG,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAU7F;;;;;;;;OAQG;IACG,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAsBjG"}
|