@synap-core/workspace-templates 0.11.0 → 0.23.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/README.md +483 -0
- package/automations/bookmark-enrichment.automation.json +321 -0
- package/automations/github-pr-events.automation.json +505 -0
- package/automations/mail-feed.automation.json +86 -0
- package/automations/ship-a-change-checks-green.automation.json +106 -0
- package/automations/ship-a-change-checks-red.automation.json +106 -0
- package/automations/ship-a-change-deployed.automation.json +56 -0
- package/automations/ship-a-change-pr-merged.automation.json +110 -0
- package/automations/ship-a-change-pr-opened.automation.json +104 -0
- package/automations/url-bookmark-capture.automation.json +132 -0
- package/capabilities/agency-skills.capability.json +157 -0
- package/capabilities/apify.capability.json +265 -0
- package/capabilities/apollo.capability.json +120 -0
- package/capabilities/arch-backend.capability.json +97 -0
- package/capabilities/arch-client-intelligence.capability.json +401 -0
- package/capabilities/backblaze-b2.capability.json +205 -0
- package/capabilities/cal-com.capability.json +266 -0
- package/capabilities/canva.capability.json +274 -0
- package/capabilities/claude-code-routines.capability.json +172 -0
- package/capabilities/claude-managed-agents.capability.json +215 -0
- package/capabilities/client-comms-report.capability.json +246 -0
- package/capabilities/content-idea-to-production.capability.json +29 -0
- package/capabilities/datagouv.capability.json +315 -0
- package/capabilities/discord-bot.capability.json +157 -0
- package/capabilities/dropbox.capability.json +281 -0
- package/capabilities/exa.capability.json +109 -0
- package/capabilities/fal-ai.capability.json +83 -0
- package/capabilities/fal-media-lab.capability.json +158 -0
- package/capabilities/fireflies.capability.json +191 -0
- package/capabilities/freellmapi.capability.json +114 -0
- package/capabilities/generic-apikey.capability.json +92 -0
- package/capabilities/github-merge.capability.json +99 -0
- package/capabilities/github-read.capability.json +325 -0
- package/capabilities/governance-tighten-recommender.capability.json +81 -0
- package/capabilities/jev.capability.json +226 -0
- package/capabilities/lead-conversion-enrollment.capability.json +246 -0
- package/capabilities/mail-feed.capability.json +277 -0
- package/capabilities/mailgun.capability.json +52 -0
- package/capabilities/nango-google.capability.json +639 -0
- package/capabilities/notion.capability.json +417 -0
- package/capabilities/omniroute.capability.json +137 -0
- package/capabilities/posthog-analytics.capability.json +258 -0
- package/capabilities/postiz.capability.json +467 -0
- package/capabilities/proactive-digest.capability.json +190 -0
- package/capabilities/proton.capability.json +288 -0
- package/capabilities/replicate-rvm.capability.json +97 -0
- package/capabilities/research-methods.capability.json +57 -0
- package/capabilities/shopify.capability.json +395 -0
- package/capabilities/slack.capability.json +369 -0
- package/capabilities/stripe.capability.json +420 -0
- package/capabilities/telegram-bridge.capability.json +56 -0
- package/capabilities/treg.capability.json +102 -0
- package/capabilities/unipile-linkedin.capability.json +471 -0
- package/capabilities/vercel.capability.json +424 -0
- package/dist/define-CPS0n8BV.d.ts +1331 -0
- package/dist/index.d.ts +189 -900
- package/dist/index.js +24654 -21639
- package/dist/index.js.map +1 -1
- package/dist/wire.d.ts +222 -0
- package/dist/wire.js +77 -0
- package/dist/wire.js.map +1 -0
- package/package.json +41 -27
- package/packs/bookmark-views-pack.json +33 -0
- package/packs/crm-pack.json +76 -0
- package/packs/customer-profile.json +28 -0
- package/packs/deep-research.playbook.json +30 -0
- package/packs/event-views-pack.json +33 -0
- package/packs/habit-profile.json +27 -0
- package/packs/hr-pack.json +54 -0
- package/packs/invoice-profile.json +28 -0
- package/packs/knowledge-base-pack.json +56 -0
- package/packs/meeting-profile.json +29 -0
- package/packs/product-profile.json +30 -0
- package/packs/project-management-pack.json +49 -0
- package/packs/recipe-profile.json +29 -0
- package/packs/task-views-pack.json +39 -0
- package/retired.yaml +136 -0
- package/skills/app-factory-build-slice/SKILL.md +70 -0
- package/skills/app-factory-build-slice.catalog.json +17 -0
- package/skills/app-factory-design-canvas/SKILL.md +96 -0
- package/skills/app-factory-design-canvas.catalog.json +17 -0
- package/skills/app-factory-design-review/SKILL.md +99 -0
- package/skills/app-factory-design-review.catalog.json +18 -0
- package/skills/app-factory-research/SKILL.md +123 -0
- package/skills/app-factory-research.catalog.json +17 -0
- package/skills/bookmark-enrichment/SKILL.md +40 -0
- package/skills/carousel-format/carousel-format.skill.json +18 -0
- package/skills/collage-broll/collage-broll.skill.json +20 -0
- package/skills/content-taste/content-taste.skill.json +18 -0
- package/skills/creative-director/creative-director.skill.json +18 -0
- package/skills/design-critique/SKILL.md +135 -0
- package/skills/design-critique.catalog.json +12 -0
- package/skills/designing-with-real-components/SKILL.md +140 -0
- package/skills/designing-with-real-components.catalog.json +12 -0
- package/skills/edit-loop/edit-loop.skill.json +25 -0
- package/skills/facecam-edit/facecam-edit.skill.json +24 -0
- package/skills/mapping-the-server/SKILL.md +54 -0
- package/skills/math-explainer-manimgx/math-explainer-manimgx.skill.json +20 -0
- package/skills/motion-quality-gates/motion-quality-gates.skill.json +20 -0
- package/skills/open-design-edit/open-design-edit.skill.json +24 -0
- package/skills/reading-client-progress/SKILL.md +65 -0
- package/skills/routing-to-channels/SKILL.md +54 -0
- package/skills/stellar-scf-grants-advisor/ASSOCIATE-CHEATSHEET.md +133 -0
- package/skills/stellar-scf-grants-advisor/ASSOCIATE-ONBOARDING-GUIDE.md +761 -0
- package/skills/stellar-scf-grants-advisor/CLIENT-FAQ.md +181 -0
- package/skills/stellar-scf-grants-advisor/PARTNER-HANDOVER.md +130 -0
- package/skills/stellar-scf-grants-advisor/SKILL.md +102 -0
- package/skills/stellar-scf-grants-advisor/examples/README.md +43 -0
- package/skills/stellar-scf-grants-advisor/examples/chat-summary-templates.md +158 -0
- package/skills/stellar-scf-grants-advisor/examples/corpus-comparable-rows.md +136 -0
- package/skills/stellar-scf-grants-advisor/examples/q10-team-rewrites.md +109 -0
- package/skills/stellar-scf-grants-advisor/examples/q2-description-rewrites.md +88 -0
- package/skills/stellar-scf-grants-advisor/examples/q6-integration-rewrites.md +107 -0
- package/skills/stellar-scf-grants-advisor/examples/tranche-structures.md +176 -0
- package/skills/stellar-scf-grants-advisor/reference/01-methodology.md +96 -0
- package/skills/stellar-scf-grants-advisor/reference/02-scoring-framework.md +199 -0
- package/skills/stellar-scf-grants-advisor/reference/03-rejection-patterns.md +241 -0
- package/skills/stellar-scf-grants-advisor/reference/04-winner-patterns.md +120 -0
- package/skills/stellar-scf-grants-advisor/reference/05-comeback-patterns.md +92 -0
- package/skills/stellar-scf-grants-advisor/reference/06-budget-benchmarks.md +125 -0
- package/skills/stellar-scf-grants-advisor/reference/07-integration-list.md +147 -0
- package/skills/stellar-scf-grants-advisor/reference/08-abstract-fields.md +258 -0
- package/skills/stellar-scf-grants-advisor/reference/09-build-fields.md +259 -0
- package/skills/stellar-scf-grants-advisor/reference/10-corpus-winners.md +135 -0
- package/skills/stellar-scf-grants-advisor/reference/11-corpus-rejects.md +267 -0
- package/skills/stellar-scf-grants-advisor/reference/12-client-communication.md +131 -0
- package/skills/stellar-scf-grants-advisor/reference/13-excel-output-spec.md +199 -0
- package/skills/stellar-scf-grants-advisor/reference/14-worked-examples.md +160 -0
- package/skills/stellar-scf-grants-advisor/templates/abstract-review-builder.py +253 -0
- package/skills/stellar-scf-grants-advisor/templates/build-review-builder.py +213 -0
- package/skills/video-direction/video-direction.skill.json +20 -0
- package/skills/video-style-vocabulary/video-style-vocabulary.skill.json +20 -0
- package/skills/video-toolbox/video-toolbox.skill.json +20 -0
- package/src/__fixtures__/lifecycle-presets.flat-before.json +205 -0
- package/src/agency-os.yaml +122 -0
- package/src/agent-os-skill-slugs.test.ts +65 -0
- package/src/apify-social-reader.test.ts +317 -0
- package/src/base.template.test.ts +25 -19
- package/src/base.yaml +95 -39
- package/src/blockchain-ecosystem.yaml +1 -0
- package/src/brand-library.yaml +525 -14
- package/src/builder-dev-fold.template.test.ts +113 -0
- package/src/builder.yaml +798 -92
- package/src/business-model-catalog-description.test.ts +47 -0
- package/src/business-model-grp.template.test.ts +214 -0
- package/src/business-model.yaml +598 -0
- package/src/catalog-safety.test.ts +198 -0
- package/src/catalog.template-items.test.ts +75 -0
- package/src/catalog.ts +85 -2
- package/src/civic-france.yaml +102 -0
- package/src/concept-guard.test.ts +28 -8
- package/src/concept-guard.ts +2 -2
- package/src/content-creation.template.test.ts +52 -0
- package/src/content-creation.yaml +192 -0
- package/src/content-os.edit-loop.template.test.ts +210 -0
- package/src/content-os.publish-due-posts.template.test.ts +261 -0
- package/src/content-os.template.test.ts +322 -0
- package/src/content-os.video-takes.template.test.ts +338 -0
- package/src/content-os.work-a-take.template.test.ts +125 -0
- package/src/content-os.yaml +1830 -39
- package/src/content-studio-retired.template.test.ts +82 -0
- package/src/corpus-flow-validity.test.ts +4 -5
- package/src/crm-absorbs-outreach.template.test.ts +117 -0
- package/src/crm-build-plan.template.test.ts +128 -0
- package/src/crm-lists.template.test.ts +96 -0
- package/src/crm-messages-cadence.template.test.ts +164 -0
- package/src/crm-one-send-door.template.test.ts +65 -0
- package/src/crm.yaml +1456 -181
- package/src/decisions-wave.template.test.ts +186 -0
- package/src/define.ts +130 -17
- package/src/design-to-implementation.template.test.ts +94 -0
- package/src/dist-freshness.test.ts +97 -0
- package/src/domain-template-type.test.ts +75 -0
- package/src/ecosystem.yaml +27 -66
- package/src/enterprise-os.yaml +10 -134
- package/src/facecam-edit.audio-studio.test.ts +188 -0
- package/src/fal-generate-image.test.ts +200 -0
- package/src/fal-media-lab-pod-files.test.ts +180 -0
- package/src/finance.yaml +254 -1
- package/src/foundation.yaml +130 -28
- package/src/frame-host-modules.d.mts +6 -0
- package/src/frame-host-modules.mjs +58 -0
- package/src/generate.mjs +9 -3
- package/src/github-merge.test.ts +210 -0
- package/src/grants.yaml +57 -17
- package/src/hr.yaml +39 -6
- package/src/index.ts +36 -1
- package/src/intent.test.ts +161 -0
- package/src/internal-runbook.yaml +19 -1
- package/src/jev.test.ts +356 -0
- package/src/legal.yaml +33 -1
- package/src/life-os.yaml +65 -240
- package/src/lifecycle-coverage.test.ts +124 -0
- package/src/lifecycle-declarations.test.ts +252 -0
- package/src/lifecycle-presets.test.ts +468 -0
- package/src/lifecycle-presets.ts +494 -0
- package/src/lifecycle-stuck.test.ts +46 -0
- package/src/lifecycle-waits.parity.test.ts +319 -0
- package/src/marketing.yaml +108 -4
- package/src/meetings-knowledge.yaml +18 -0
- package/src/networking.yaml +23 -2
- package/src/operations.yaml +304 -4
- package/src/pipeline-views.template.test.ts +88 -0
- package/src/playbook-goal-placeholders.test.ts +57 -0
- package/src/postiz-contract.test.ts +134 -0
- package/src/presentation-declarations.test.ts +174 -0
- package/src/process-contract.validate.test.ts +352 -0
- package/src/project-management.yaml +39 -740
- package/src/publish-body.d.mts +29 -0
- package/src/publish-body.mjs +74 -0
- package/src/publish-body.test.ts +74 -0
- package/src/publish-official-lib.test.ts +176 -0
- package/src/replicate-rvm.test.ts +504 -0
- package/src/research-base.yaml +119 -20
- package/src/research-decision.yaml +60 -3
- package/src/reserved-profile-slugs.test.ts +83 -0
- package/src/retired-goal-kinds.test.ts +58 -0
- package/src/role-reference-and-step-domain.test.ts +192 -0
- package/src/seed-obligation.test.ts +176 -0
- package/src/send-message-maps.test.ts +227 -0
- package/src/shared-role-ssot.test.ts +42 -1
- package/src/ship-a-change.template.test.ts +177 -0
- package/src/skills.test.ts +170 -0
- package/src/social.yaml +85 -27
- package/src/space-packages.freshness.test.ts +97 -0
- package/src/space-packages.generated.ts +40 -0
- package/src/space-packages.test.ts +125 -0
- package/src/space-packages.ts +73 -0
- package/src/start.test.ts +188 -0
- package/src/task-intents.test.ts +293 -0
- package/src/templates.ts +21639 -21522
- package/src/types.ts +443 -60
- package/src/unipile-publish-post.test.ts +162 -0
- package/src/validate-flow.drift.test.ts +2 -3
- package/src/validate-flow.ts +32 -2
- package/src/validate-onboarding.test.ts +59 -0
- package/src/validate.test.ts +156 -3
- package/src/validate.ts +1384 -7
- package/src/wire-core.ts +231 -0
- package/src/wire.test.ts +159 -0
- package/src/wire.ts +107 -0
- package/src/__fixtures__/report-automation-flow.json +0 -393
- package/src/agent-fleet.yaml +0 -1645
- package/src/business-developer.template.test.ts +0 -351
- package/src/business-developer.yaml +0 -860
- package/src/content-studio.yaml +0 -361
- package/src/dev-dashboard.yaml +0 -990
- package/src/outreach-comms.yaml +0 -486
- package/src/the-arch.yaml +0 -201
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lifecycle DECLARATIONS (work model L1) — the catalog declares each kind's and
|
|
3
|
+
* role's ONE lifecycle, so no board, pipeline or governance rung has to guess
|
|
4
|
+
* what a status means from its name.
|
|
5
|
+
*
|
|
6
|
+
* Pinned:
|
|
7
|
+
* - the validator's category list equals `@synap-core/types/lifecycle`;
|
|
8
|
+
* - the mandatory declarations (deal, lead, client, the post that Produce
|
|
9
|
+
* Content drives) carry the founder-confirmed categories;
|
|
10
|
+
* - COVERAGE, derived: every non-system kind a playbook moves through
|
|
11
|
+
* `subjectProfile.statusProperty` declares that property as its lifecycle;
|
|
12
|
+
* - `humanOnly` = the binding playbooks' `humanOnlyStatuses` that stop, pause
|
|
13
|
+
* or end the thing (category paused/done/lost) — never an invented one —
|
|
14
|
+
* plus the exact ledger of founder-decided ACTIVE person-only options
|
|
15
|
+
* (`PERSON_ONLY_ACTIVE`);
|
|
16
|
+
* - the whole corpus validates with zero `lifecycle-*` errors, and each rule
|
|
17
|
+
* fires on a broken clone (self-checks).
|
|
18
|
+
*/
|
|
19
|
+
import { describe, expect, it } from "vitest";
|
|
20
|
+
import { LIFECYCLE_CATEGORIES as TYPES_CATEGORIES } from "@synap-core/types/lifecycle";
|
|
21
|
+
import { LIFECYCLE_CATEGORIES, SYSTEM_PROFILES, validateTemplate } from "./validate.js";
|
|
22
|
+
import type { TemplateLifecycle, WorkspaceYaml } from "./types.js";
|
|
23
|
+
import { readYamlTemplates } from "./generate.mjs";
|
|
24
|
+
|
|
25
|
+
const corpus = readYamlTemplates().map((t) => t.data as WorkspaceYaml);
|
|
26
|
+
const tpl = (slug: string) => corpus.find((t) => t.meta.slug === slug)!;
|
|
27
|
+
const lifecycleOf = (t: WorkspaceYaml, profile: string): TemplateLifecycle | undefined =>
|
|
28
|
+
t.profiles.find((p) => p.slug === profile)?.lifecycle;
|
|
29
|
+
const categories = (lc: TemplateLifecycle | undefined) =>
|
|
30
|
+
Object.fromEntries((lc?.options ?? []).map((o) => [o.key, o.category]));
|
|
31
|
+
const lifecycleErrors = (t: WorkspaceYaml, c: WorkspaceYaml[] = corpus) =>
|
|
32
|
+
validateTemplate(t, { corpus: c }).errors.filter((e) => e.rule.startsWith("lifecycle-"));
|
|
33
|
+
|
|
34
|
+
describe("lifecycle declarations — the category vocabulary", () => {
|
|
35
|
+
it("the validator's mirror equals @synap-core/types/lifecycle", () => {
|
|
36
|
+
expect([...LIFECYCLE_CATEGORIES]).toEqual([...TYPES_CATEGORIES]);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("lifecycle declarations — the mandatory four", () => {
|
|
41
|
+
it("deal = commercialStage; lost is a person's call", () => {
|
|
42
|
+
for (const slug of ["crm"]) {
|
|
43
|
+
const lc = lifecycleOf(tpl(slug), "deal");
|
|
44
|
+
expect(lc?.property, slug).toBe("commercialStage");
|
|
45
|
+
expect(categories(lc)).toEqual({ draft: "open", proposed: "active", negotiating: "active", won: "done", lost: "lost" });
|
|
46
|
+
expect(lc?.options.filter((o) => o.humanOnly).map((o) => o.key)).toEqual(["lost"]);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("lead = leadStage with the W15 categories", () => {
|
|
51
|
+
const lc = lifecycleOf(tpl("foundation"), "lead");
|
|
52
|
+
expect(lc?.property).toBe("leadStage");
|
|
53
|
+
expect(categories(lc)).toEqual({
|
|
54
|
+
new: "open",
|
|
55
|
+
researching: "open",
|
|
56
|
+
outreach: "open",
|
|
57
|
+
engaged: "active",
|
|
58
|
+
qualified: "active",
|
|
59
|
+
nurturing: "active",
|
|
60
|
+
converted: "done",
|
|
61
|
+
disqualified: "lost",
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("client = clientStatus, churned is a lost status (never a role)", () => {
|
|
66
|
+
for (const slug of ["foundation", "grants"]) {
|
|
67
|
+
const lc = lifecycleOf(tpl(slug), "client");
|
|
68
|
+
expect(lc?.property, slug).toBe("clientStatus");
|
|
69
|
+
expect(categories(lc).churned, slug).toBe("lost");
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("the post Produce Content drives declares post-status", () => {
|
|
74
|
+
const produce = tpl("content-os").playbooks!.find((p) => p.name === "Produce Content")!;
|
|
75
|
+
expect(produce.subjectProfile).toMatchObject({ profileSlug: "post", statusProperty: "post-status" });
|
|
76
|
+
const lc = lifecycleOf(tpl("content-os"), "post");
|
|
77
|
+
expect(lc?.property).toBe("post-status");
|
|
78
|
+
expect(categories(lc)).toMatchObject({ Idea: "open", Published: "done", Archived: "lost" });
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Every (kind, statusProperty, playbook) a catalog playbook moves. `withSystem`
|
|
84
|
+
* false drops the pod's system kinds (task, decision, question…), whose
|
|
85
|
+
* lifecycle the pod seeds itself (`ensure-system-lifecycles.ts`) — except
|
|
86
|
+
* `deal`, a system kind whose lifecycle the CRM template owns.
|
|
87
|
+
*/
|
|
88
|
+
const TEMPLATE_OWNED_SYSTEM_LIFECYCLES = new Set(["deal"]);
|
|
89
|
+
function playbookBindings({ withSystem = false } = {}) {
|
|
90
|
+
return corpus.flatMap((t) =>
|
|
91
|
+
(t.playbooks ?? [])
|
|
92
|
+
.filter(
|
|
93
|
+
(pb) =>
|
|
94
|
+
pb.subjectProfile?.statusProperty &&
|
|
95
|
+
(withSystem ||
|
|
96
|
+
!SYSTEM_PROFILES.has(pb.subjectProfile.profileSlug) ||
|
|
97
|
+
TEMPLATE_OWNED_SYSTEM_LIFECYCLES.has(pb.subjectProfile.profileSlug))
|
|
98
|
+
)
|
|
99
|
+
.map((pb) => ({ template: t, pb, kind: pb.subjectProfile!.profileSlug, property: pb.subjectProfile!.statusProperty! }))
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
describe("lifecycle declarations — the dev lifecycles (founder, 2026-10-13)", () => {
|
|
104
|
+
const shape = (profile: string) =>
|
|
105
|
+
lifecycleOf(tpl("builder-workspace"), profile)?.options.map((o) => [o.label, o.category, Boolean(o.humanOnly)]);
|
|
106
|
+
|
|
107
|
+
it("feature = featureStatus: Idea · Spec · Building · Review · Shipped · Dropped", () => {
|
|
108
|
+
expect(lifecycleOf(tpl("builder-workspace"), "devplane_feature")?.property).toBe("featureStatus");
|
|
109
|
+
expect(shape("devplane_feature")).toEqual([
|
|
110
|
+
["Idea", "open", false],
|
|
111
|
+
["Spec", "active", false],
|
|
112
|
+
["Building", "active", false],
|
|
113
|
+
["Review", "active", false],
|
|
114
|
+
["Shipped", "done", false],
|
|
115
|
+
["Dropped", "lost", true],
|
|
116
|
+
]);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("story = storyStatus: Draft · Ready · In feature · Done · Dropped", () => {
|
|
120
|
+
expect(lifecycleOf(tpl("builder-workspace"), "devplane_story")?.property).toBe("storyStatus");
|
|
121
|
+
expect(shape("devplane_story")).toEqual([
|
|
122
|
+
["Draft", "open", false],
|
|
123
|
+
["Ready", "active", false],
|
|
124
|
+
["In feature", "active", false],
|
|
125
|
+
["Done", "done", false],
|
|
126
|
+
["Dropped", "lost", true],
|
|
127
|
+
]);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("lifecycle declarations — coverage (derived from the playbooks)", () => {
|
|
132
|
+
it("non-vacuity: the scan sees the bindings it must", () => {
|
|
133
|
+
const kinds = new Set(playbookBindings().map((b) => b.kind));
|
|
134
|
+
expect(kinds.size).toBeGreaterThanOrEqual(14);
|
|
135
|
+
expect(kinds.has("post")).toBe(true);
|
|
136
|
+
expect(kinds.has("deal")).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("every kind a playbook moves declares that property as its lifecycle, wherever it is declared", () => {
|
|
140
|
+
const missing: string[] = [];
|
|
141
|
+
for (const b of playbookBindings())
|
|
142
|
+
for (const t of corpus)
|
|
143
|
+
for (const p of t.profiles.filter((x) => x.slug === b.kind && x.properties.some((pr) => pr.slug === b.property)))
|
|
144
|
+
if (p.lifecycle?.property !== b.property) missing.push(`${t.meta.slug}:${b.kind}.${b.property}`);
|
|
145
|
+
expect([...new Set(missing)]).toEqual([]);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* ACTIVE options a person must set, each a founder decision — the only place
|
|
150
|
+
* `humanOnly` may sit outside paused/done/lost. Still never invented: each
|
|
151
|
+
* must ALSO be in its binding playbook's `humanOnlyStatuses` (the rule below
|
|
152
|
+
* reads both). EXACT: an entry that is no longer humanOnly turns this red.
|
|
153
|
+
* - ui-design.approved — the design review is a person's call; a coding
|
|
154
|
+
* agent implements only from an approved design (DESIGN-DEV-XP-PLAN P6,
|
|
155
|
+
* founder-approved 2026-10-12).
|
|
156
|
+
* - outreach-touch.sent — the agent drafts, a person sends (founder decision
|
|
157
|
+
* 2026-10-20; the CRM absorbed Outreach & Comms 2026-10-23): no agent ever
|
|
158
|
+
* marks a message sent, and the reply reader starts only once a person did.
|
|
159
|
+
*/
|
|
160
|
+
const PERSON_ONLY_ACTIVE = new Set(["builder-workspace:ui-design.approved", "crm:outreach-touch.sent"]);
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Person-only options the FOUNDER decided on a lifecycle no playbook moves
|
|
164
|
+
* yet (so no `humanOnlyStatuses` can carry them). Each one a dated decision;
|
|
165
|
+
* EXACT, like the ledger above.
|
|
166
|
+
* - devplane_feature.cancelled / devplane_story.dropped — "Dropped (lost,
|
|
167
|
+
* humanOnly)", object-first homes decision 2026-10-13.
|
|
168
|
+
*/
|
|
169
|
+
const FOUNDER_HUMAN_ONLY = new Set([
|
|
170
|
+
"builder-workspace:devplane_feature.cancelled",
|
|
171
|
+
"builder-workspace:devplane_story.dropped",
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
it("humanOnly = the binding playbooks' humanOnlyStatuses that pause or end the thing", () => {
|
|
175
|
+
const drift: string[] = [];
|
|
176
|
+
const activeSeen: string[] = [];
|
|
177
|
+
const founderSeen: string[] = [];
|
|
178
|
+
for (const t of corpus)
|
|
179
|
+
for (const p of t.profiles) {
|
|
180
|
+
if (!p.lifecycle) continue;
|
|
181
|
+
const ho = new Set(
|
|
182
|
+
playbookBindings({ withSystem: true })
|
|
183
|
+
.filter((b) => b.kind === p.slug && b.property === p.lifecycle!.property)
|
|
184
|
+
.flatMap((b) => b.pb.humanOnlyStatuses ?? [])
|
|
185
|
+
);
|
|
186
|
+
for (const o of p.lifecycle.options) {
|
|
187
|
+
const id = `${t.meta.slug}:${p.slug}.${o.key}`;
|
|
188
|
+
const want =
|
|
189
|
+
(ho.has(o.key) && (["paused", "done", "lost"].includes(o.category) || PERSON_ONLY_ACTIVE.has(id))) ||
|
|
190
|
+
FOUNDER_HUMAN_ONLY.has(id);
|
|
191
|
+
if (want && PERSON_ONLY_ACTIVE.has(id)) activeSeen.push(id);
|
|
192
|
+
if (o.humanOnly && FOUNDER_HUMAN_ONLY.has(id)) founderSeen.push(id);
|
|
193
|
+
if (Boolean(o.humanOnly) !== want) drift.push(`${id} humanOnly=${Boolean(o.humanOnly)}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
expect(drift).toEqual([]);
|
|
197
|
+
expect(activeSeen.sort()).toEqual([...PERSON_ONLY_ACTIVE].sort());
|
|
198
|
+
expect(founderSeen.sort()).toEqual([...FOUNDER_HUMAN_ONLY].sort());
|
|
199
|
+
// non-vacuity: the rule marks real options
|
|
200
|
+
expect(lifecycleOf(tpl("content-os"), "post")?.options.find((o) => o.key === "Archived")?.humanOnly).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe("lifecycle declarations — the validator", () => {
|
|
205
|
+
it("the corpus has zero lifecycle errors", () => {
|
|
206
|
+
expect(corpus.flatMap((t) => lifecycleErrors(t).map((e) => `${t.meta.slug} ${e.rule}: ${e.message}`))).toEqual([]);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const clone = () => structuredClone(corpus);
|
|
210
|
+
const crmIn = (c: WorkspaceYaml[]) => c.find((t) => t.meta.slug === "crm")!;
|
|
211
|
+
const dealIn = (c: WorkspaceYaml[]) => crmIn(c).profiles.find((p) => p.slug === "deal")!;
|
|
212
|
+
|
|
213
|
+
it("self-check: options must be the enumValues, same order", () => {
|
|
214
|
+
const c = clone();
|
|
215
|
+
dealIn(c).lifecycle!.options.reverse();
|
|
216
|
+
expect(lifecycleErrors(crmIn(c), c).map((e) => e.rule)).toContain("lifecycle-options-mismatch");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("self-check: an unknown category and an unknown property are refused", () => {
|
|
220
|
+
const c = clone();
|
|
221
|
+
(dealIn(c).lifecycle!.options[0] as { category: string }).category = "closed";
|
|
222
|
+
expect(lifecycleErrors(crmIn(c), c).map((e) => e.rule)).toContain("lifecycle-category");
|
|
223
|
+
dealIn(c).lifecycle!.property = "nope";
|
|
224
|
+
expect(lifecycleErrors(crmIn(c), c).map((e) => e.rule)).toContain("lifecycle-property-unknown");
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("self-check: two templates declaring one profile differently diverge", () => {
|
|
228
|
+
// `campaign` is declared with its lifecycle by marketing, content-os and crm
|
|
229
|
+
// (deal is declared with one only by crm since business-developer folded in).
|
|
230
|
+
const campaignIn = (c: WorkspaceYaml[], slug: string) =>
|
|
231
|
+
c.find((t) => t.meta.slug === slug)!.profiles.find((p) => p.slug === "campaign")!;
|
|
232
|
+
const c = clone();
|
|
233
|
+
campaignIn(c, "crm").lifecycle!.options[1].category = "open";
|
|
234
|
+
const marketing = c.find((t) => t.meta.slug === "marketing-campaign")!;
|
|
235
|
+
expect(lifecycleErrors(marketing, c).map((e) => e.rule)).toContain("lifecycle-diverges");
|
|
236
|
+
// dropping it from one declaration diverges too (reported where it is missing)
|
|
237
|
+
const c2 = clone();
|
|
238
|
+
delete campaignIn(c2, "crm").lifecycle;
|
|
239
|
+
expect(lifecycleErrors(crmIn(c2), c2).map((e) => e.rule)).toContain("lifecycle-diverges");
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("self-check: a playbook moving another property than the lifecycle is refused", () => {
|
|
243
|
+
const c = clone();
|
|
244
|
+
const produce = c.find((t) => t.meta.slug === "content-os")!.playbooks!.find((p) => p.name === "Produce Content")!;
|
|
245
|
+
const post = c.find((t) => t.meta.slug === "content-os")!.profiles.find((p) => p.slug === "post")!;
|
|
246
|
+
post.lifecycle!.property = "platform";
|
|
247
|
+
produce.subjectProfile!.statusProperty = "post-status";
|
|
248
|
+
expect(lifecycleErrors(c.find((t) => t.meta.slug === "content-os")!, c).map((e) => e.rule)).toContain(
|
|
249
|
+
"lifecycle-status-property"
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
});
|