@thebackstoryis/engineering-with-ai 0.2.9
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/Docs/README.md +50 -0
- package/Docs/adoption/consultancy-and-multi-project-rollout.md +135 -0
- package/Docs/adoption/non-technical-team-guide.md +126 -0
- package/Docs/archaeology-technology-and-hosting-discovery.md +212 -0
- package/Docs/blast-radius-and-impact-routing-guide.md +325 -0
- package/Docs/blueprints/internal-blueprint-catalogue.md +146 -0
- package/Docs/blueprints/maintaining-organisation-blueprints.md +154 -0
- package/Docs/blueprints/validation-and-troubleshooting.md +168 -0
- package/Docs/cli-reference.md +113 -0
- package/Docs/completed-phase-evidence-amendments.md +74 -0
- package/Docs/consultancy-network-rollout-control-plane-guide.md +202 -0
- package/Docs/context-aware-delivery-companion-guide.md +198 -0
- package/Docs/context-aware-delivery-companion-user-guide.md +184 -0
- package/Docs/context-management-and-token-efficiency.md +113 -0
- package/Docs/design-systems/design-system-implementation-guide.md +85 -0
- package/Docs/design-systems/design-system-pack-authoring-guide.md +95 -0
- package/Docs/design-systems/design-system-review-guide.md +51 -0
- package/Docs/design-systems/design-system-user-guide.md +96 -0
- package/Docs/design-systems/product-owner-guide.md +49 -0
- package/Docs/designing-organisation-blueprint-packs.md +384 -0
- package/Docs/developer-delivery-guide.md +224 -0
- package/Docs/error-reporting-guide.md +110 -0
- package/Docs/error-reporting-provider-guide.md +49 -0
- package/Docs/examples/error-report-adapter.md +70 -0
- package/Docs/examples/meeting-review.md +76 -0
- package/Docs/examples/minimal-design-system.md +67 -0
- package/Docs/examples/prototype-review-inputs.md +175 -0
- package/Docs/examples/reproducible-archaeology-depth-example.md +144 -0
- package/Docs/examples/test-scenario-input.md +68 -0
- package/Docs/examples/worked-examples.md +147 -0
- package/Docs/existing-project-onboarding-guide.md +214 -0
- package/Docs/explanation/core-concepts.md +26 -0
- package/Docs/explanation/delivery-workflow.md +48 -0
- package/Docs/governance/governance-team-guide.md +139 -0
- package/Docs/governed-starter-project-materialisation-guide.md +284 -0
- package/Docs/guide-catalogue.md +117 -0
- package/Docs/guided-discovery-facilitator-guide.md +172 -0
- package/Docs/guided-intent-workspace-guide.md +109 -0
- package/Docs/guided-phase-evidence-drafting-guide.md +119 -0
- package/Docs/human-approval-and-assurance-guide.md +146 -0
- package/Docs/knowledge-proposals-implementer-guide.md +106 -0
- package/Docs/knowledge-proposals-user-guide.md +247 -0
- package/Docs/maintainers/context-benchmarks.md +29 -0
- package/Docs/maintainers/contributing.md +58 -0
- package/Docs/maintainers/evidence-depth-acceptance.md +72 -0
- package/Docs/maintainers/verification-walkthroughs.md +104 -0
- package/Docs/meeting-evidence-implementer-guide.md +132 -0
- package/Docs/meeting-evidence-user-guide.md +200 -0
- package/Docs/operations/dashboard-and-delivery-state.md +153 -0
- package/Docs/operations/dashboard-configuration.md +87 -0
- package/Docs/operations/installation-updating-and-entitlements.md +135 -0
- package/Docs/operations/premium-personas-setup.md +76 -0
- package/Docs/operations/troubleshooting-and-recovery.md +205 -0
- package/Docs/organisation-rollout-guide.md +142 -0
- package/Docs/persona-entitlement-provider-guide.md +199 -0
- package/Docs/persona-guided-prototype-iteration.md +129 -0
- package/Docs/personas/organisation-specific-personas.md +103 -0
- package/Docs/personas/persona-authoring-cookbook.md +176 -0
- package/Docs/personas/persona-engagement-ui.md +133 -0
- package/Docs/personas/persona-governance.md +118 -0
- package/Docs/platform-export-analysis-guide.md +336 -0
- package/Docs/policies/governance-owner-guide.md +36 -0
- package/Docs/policies/implementation-guide.md +42 -0
- package/Docs/policies/organisation-policy-design-gates.md +58 -0
- package/Docs/policies/policy-pack-authoring-guide.md +108 -0
- package/Docs/policies/product-owner-guide.md +43 -0
- package/Docs/policies/technical-owner-guide.md +37 -0
- package/Docs/product-owner-guide.md +327 -0
- package/Docs/project-portfolio-orchestration-guide.md +199 -0
- package/Docs/quality/manual-qa-and-acceptance.md +162 -0
- package/Docs/quality/persona-driven-test-scenarios.md +172 -0
- package/Docs/quality/reproducible-archaeology-depth-review-checklist.md +89 -0
- package/Docs/reference/capabilities-and-project-layout.md +678 -0
- package/Docs/reference/cli-and-configuration.md +398 -0
- package/Docs/reference/contributions-api.md +23 -0
- package/Docs/reference/security-adapter-authoring.md +81 -0
- package/Docs/reference/starter-adapter-authoring.md +74 -0
- package/Docs/repository-source-map-guide.md +381 -0
- package/Docs/reproducible-archaeology-and-discovery-depth.md +292 -0
- package/Docs/screen-prototype-creation-guide.md +324 -0
- package/Docs/security-validation-guide.md +353 -0
- package/Docs/solution-readiness-review-guide.md +123 -0
- package/Docs/standards/project-standards-authoring.md +157 -0
- package/Docs/team-hub-guide.md +162 -0
- package/Docs/team-hub-resource-registry-guide.md +167 -0
- package/Docs/tutorials/first-delivery.md +83 -0
- package/Docs/tutorials/first-session.md +62 -0
- package/Docs/using-lifecycle-hooks.md +381 -0
- package/Docs/working-with-personas.md +274 -0
- package/LICENSE +165 -0
- package/README.md +96 -0
- package/agents-src/claude/ewai-security-reviewer.md +15 -0
- package/bin/ewai +5 -0
- package/config/archaeology-record-families.yaml +59 -0
- package/config/delivery-artifacts.yaml +121 -0
- package/config/delivery-stages.yaml +77 -0
- package/config/design-system.schema.json +46 -0
- package/config/error-reporting.schema.json +79 -0
- package/config/evidence-depth.schema.json +53 -0
- package/config/intent.schema.json +90 -0
- package/config/knowledge-proposals-proposal.schema.json +34 -0
- package/config/lifecycle-event.schema.json +68 -0
- package/config/lifecycle-handler.schema.json +45 -0
- package/config/lifecycle-hook-ack.schema.json +19 -0
- package/config/meeting-evidence-candidate.schema.json +102 -0
- package/config/organisation-policy.schema.json +137 -0
- package/config/pack.schema.json +250 -0
- package/config/persona-pack.schema.json +21 -0
- package/config/persona.schema.json +17 -0
- package/config/policy-evaluation.schema.json +77 -0
- package/config/policy-facts.schema.json +140 -0
- package/config/portfolio.schema.json +68 -0
- package/config/project.schema.json +313 -0
- package/config/prototype-iteration.schema.json +128 -0
- package/config/rollout.schema.json +87 -0
- package/config/security-adapter.schema.json +31 -0
- package/config/security-scan-request.schema.json +64 -0
- package/config/security-scan-response.schema.json +52 -0
- package/config/security-validation-policy.schema.json +74 -0
- package/config/starter-source-acknowledgement.schema.json +13 -0
- package/config/starter-source-adapter.schema.json +38 -0
- package/config/starter-source-request.schema.json +61 -0
- package/package.json +77 -0
- package/packs/core/pack.yaml +7 -0
- package/packs/design-systems/default/experience-promise.md +9 -0
- package/packs/design-systems/default/intentional-review.md +10 -0
- package/packs/design-systems/default/interaction-and-entry.md +9 -0
- package/packs/design-systems/default/meaningful-content-and-states.md +9 -0
- package/packs/design-systems/default/pack.yaml +44 -0
- package/packs/design-systems/default/principles.md +10 -0
- package/packs/personas/core/pack.yaml +7 -0
- package/packs/personas/core/personas/archaeologist.md +37 -0
- package/packs/personas/core/personas/end-user.md +17 -0
- package/packs/personas/core/personas/maintainer.md +17 -0
- package/packs/personas/core/personas/operator.md +17 -0
- package/packs/personas/core/personas/specs-knowledge-curator.md +35 -0
- package/packs/technologies/laravel/pack.yaml +30 -0
- package/packs/technologies/laravel-nuxt/pack.yaml +30 -0
- package/packs/technologies/nuxt/pack.yaml +30 -0
- package/packs/technologies/power-platform/pack.yaml +31 -0
- package/packs/technologies/salesforce/pack.yaml +25 -0
- package/public/app.js +4896 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/assets/backstory-icon.png +0 -0
- package/public/dashboard-navigation.js +98 -0
- package/public/favicon-16.png +0 -0
- package/public/favicon-32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +789 -0
- package/public/styles.css +2693 -0
- package/public/team-hub/app.js +202 -0
- package/public/team-hub/index.html +79 -0
- package/public/team-hub/styles.css +90 -0
- package/scripts/publication-check.mjs +140 -0
- package/scripts/setup.mjs +21 -0
- package/skills-src/ewai-archaeology/SKILL.md +334 -0
- package/skills-src/ewai-archaeology/agents/openai.yaml +4 -0
- package/skills-src/ewai-archaeology/references/archaeology-contract.md +201 -0
- package/skills-src/ewai-archaeology/references/lifecycle-reconstruction.md +177 -0
- package/skills-src/ewai-archaeology/references/maximum-detail-reconstruction.md +97 -0
- package/skills-src/ewai-archaeology/references/model-routing.md +26 -0
- package/skills-src/ewai-architecture/SKILL.md +108 -0
- package/skills-src/ewai-architecture/agents/openai.yaml +4 -0
- package/skills-src/ewai-architecture/references/architecture-contract.md +176 -0
- package/skills-src/ewai-context/SKILL.md +68 -0
- package/skills-src/ewai-context/agents/openai.yaml +4 -0
- package/skills-src/ewai-context-import/SKILL.md +118 -0
- package/skills-src/ewai-context-import/agents/openai.yaml +4 -0
- package/skills-src/ewai-context-import/references/context-import-contract.md +106 -0
- package/skills-src/ewai-dashboard-configuration/SKILL.md +20 -0
- package/skills-src/ewai-deliver/SKILL.md +122 -0
- package/skills-src/ewai-deliver/references/delivery-evidence.md +92 -0
- package/skills-src/ewai-deliver/references/phase-routing.md +31 -0
- package/skills-src/ewai-design-system-apply/SKILL.md +27 -0
- package/skills-src/ewai-design-system-apply/agents/openai.yaml +4 -0
- package/skills-src/ewai-design-system-apply/references/application-contract.md +36 -0
- package/skills-src/ewai-design-system-author/SKILL.md +28 -0
- package/skills-src/ewai-design-system-author/agents/openai.yaml +4 -0
- package/skills-src/ewai-design-system-author/references/authoring-contract.md +38 -0
- package/skills-src/ewai-design-system-review/SKILL.md +26 -0
- package/skills-src/ewai-design-system-review/agents/openai.yaml +4 -0
- package/skills-src/ewai-design-system-review/references/review-contract.md +40 -0
- package/skills-src/ewai-error-reporting/SKILL.md +46 -0
- package/skills-src/ewai-error-reporting/agents/openai.yaml +4 -0
- package/skills-src/ewai-error-reporting/references/provider-contract.md +74 -0
- package/skills-src/ewai-evidence-depth/SKILL.md +72 -0
- package/skills-src/ewai-evidence-depth/agents/openai.yaml +4 -0
- package/skills-src/ewai-evidence-depth/references/evidence-depth-contract.md +127 -0
- package/skills-src/ewai-intent/SKILL.md +68 -0
- package/skills-src/ewai-intent/agents/openai.yaml +4 -0
- package/skills-src/ewai-intent/references/intent-contract.md +42 -0
- package/skills-src/ewai-knowledge-proposals/SKILL.md +105 -0
- package/skills-src/ewai-knowledge-proposals/agents/openai.yaml +4 -0
- package/skills-src/ewai-knowledge-proposals/references/proposal-contract.md +59 -0
- package/skills-src/ewai-meeting-evidence/SKILL.md +106 -0
- package/skills-src/ewai-meeting-evidence/agents/openai.yaml +4 -0
- package/skills-src/ewai-meeting-evidence/references/candidate-contract.md +64 -0
- package/skills-src/ewai-organisation-policy/SKILL.md +62 -0
- package/skills-src/ewai-organisation-policy/agents/openai.yaml +4 -0
- package/skills-src/ewai-organisation-policy/references/policy-contract.md +94 -0
- package/skills-src/ewai-palace-housekeeping/SKILL.md +55 -0
- package/skills-src/ewai-palace-housekeeping/agents/openai.yaml +4 -0
- package/skills-src/ewai-persona-entitlement/SKILL.md +60 -0
- package/skills-src/ewai-persona-entitlement/agents/openai.yaml +4 -0
- package/skills-src/ewai-phase-evidence/SKILL.md +79 -0
- package/skills-src/ewai-phase-evidence/agents/openai.yaml +4 -0
- package/skills-src/ewai-pipeline/SKILL.md +130 -0
- package/skills-src/ewai-pipeline/agents/openai.yaml +4 -0
- package/skills-src/ewai-pipeline/references/cli.md +86 -0
- package/skills-src/ewai-pipeline/references/specs-contract.md +16 -0
- package/skills-src/ewai-portfolio/SKILL.md +70 -0
- package/skills-src/ewai-portfolio/agents/openai.yaml +4 -0
- package/skills-src/ewai-portfolio/references/portfolio-contract.md +67 -0
- package/skills-src/ewai-project-discovery/SKILL.md +95 -0
- package/skills-src/ewai-project-discovery/agents/openai.yaml +4 -0
- package/skills-src/ewai-project-discovery/references/discovery-contract.md +34 -0
- package/skills-src/ewai-prototype-iteration/SKILL.md +30 -0
- package/skills-src/ewai-prototype-iteration/agents/openai.yaml +4 -0
- package/skills-src/ewai-prototype-iteration/references/review-contract.md +49 -0
- package/skills-src/ewai-retro/SKILL.md +48 -0
- package/skills-src/ewai-retro/agents/openai.yaml +4 -0
- package/skills-src/ewai-retro/references/asset-routing.md +14 -0
- package/skills-src/ewai-rollout/SKILL.md +74 -0
- package/skills-src/ewai-rollout/agents/openai.yaml +4 -0
- package/skills-src/ewai-rollout/references/rollout-contract.md +74 -0
- package/skills-src/ewai-shape-intents/SKILL.md +84 -0
- package/skills-src/ewai-shape-intents/agents/openai.yaml +4 -0
- package/skills-src/ewai-shape-intents/references/intent-mapping-contract.md +109 -0
- package/skills-src/ewai-solution-readiness/SKILL.md +55 -0
- package/skills-src/ewai-solution-readiness/agents/openai.yaml +4 -0
- package/skills-src/ewai-standards-check/SKILL.md +93 -0
- package/skills-src/ewai-standards-check/agents/openai.yaml +4 -0
- package/skills-src/ewai-standards-check/references/report-contract.md +116 -0
- package/skills-src/ewai-test-scenarios/SKILL.md +94 -0
- package/skills-src/ewai-test-scenarios/agents/openai.yaml +4 -0
- package/skills-src/ewai-test-scenarios/references/scenario-contract.md +88 -0
- package/src/afk-worker.mjs +16 -0
- package/src/archaeology.mjs +1333 -0
- package/src/checkin.mjs +261 -0
- package/src/cli.mjs +2427 -0
- package/src/companion-guidance.mjs +257 -0
- package/src/companion-opening.mjs +62 -0
- package/src/companion.mjs +256 -0
- package/src/context.mjs +210 -0
- package/src/dashboard-preferences.mjs +80 -0
- package/src/delivery-artifacts.mjs +204 -0
- package/src/delivery-documents.mjs +248 -0
- package/src/delivery-gates.mjs +317 -0
- package/src/delivery.mjs +1433 -0
- package/src/design-system-application.mjs +291 -0
- package/src/design-system-authoring.mjs +101 -0
- package/src/design-systems.mjs +466 -0
- package/src/discovery.mjs +1314 -0
- package/src/error-reporting.mjs +323 -0
- package/src/evidence-depth.mjs +543 -0
- package/src/execution-state.mjs +243 -0
- package/src/install.mjs +166 -0
- package/src/intent-dependencies.mjs +117 -0
- package/src/intent-maps.mjs +402 -0
- package/src/intents.mjs +747 -0
- package/src/knowledge-proposals.mjs +717 -0
- package/src/launcher.mjs +51 -0
- package/src/meeting-evidence.mjs +703 -0
- package/src/network-rollout.mjs +386 -0
- package/src/organisation-blueprints.mjs +438 -0
- package/src/organisation-policies.mjs +448 -0
- package/src/packs.mjs +44 -0
- package/src/paths.mjs +62 -0
- package/src/persona-entitlements.mjs +438 -0
- package/src/persona-licence-config.mjs +98 -0
- package/src/persona-website-provider.mjs +134 -0
- package/src/persona-zip.mjs +87 -0
- package/src/personas.mjs +159 -0
- package/src/platform-metadata-analysis.mjs +314 -0
- package/src/policy-design-gates.mjs +623 -0
- package/src/policy-gate-integration.mjs +318 -0
- package/src/portfolio.mjs +509 -0
- package/src/power-platform-source-map.mjs +190 -0
- package/src/project.mjs +449 -0
- package/src/prototype-iterations.mjs +730 -0
- package/src/repository-source-map.mjs +603 -0
- package/src/runtime/afk-conductor.mjs +973 -0
- package/src/runtime/context-assembly.mjs +457 -0
- package/src/runtime/context-benchmarks.mjs +115 -0
- package/src/runtime/dashboard-actions.mjs +109 -0
- package/src/runtime/dashboard-handoffs.mjs +149 -0
- package/src/runtime/dashboard-server.mjs +1272 -0
- package/src/runtime/dashboard.mjs +197 -0
- package/src/runtime/database.mjs +789 -0
- package/src/runtime/error-reporting.mjs +581 -0
- package/src/runtime/evidence-depth-workspace.mjs +412 -0
- package/src/runtime/execution-leases.mjs +299 -0
- package/src/runtime/guided-discovery.mjs +350 -0
- package/src/runtime/guided-intents.mjs +517 -0
- package/src/runtime/impact-analysis.mjs +535 -0
- package/src/runtime/intents.mjs +222 -0
- package/src/runtime/knowledge.mjs +86 -0
- package/src/runtime/lifecycle-hooks.mjs +1239 -0
- package/src/runtime/mcp-config.mjs +110 -0
- package/src/runtime/mcp-server.mjs +1885 -0
- package/src/runtime/palace.mjs +362 -0
- package/src/runtime/paths.mjs +58 -0
- package/src/runtime/persona-engagement.mjs +255 -0
- package/src/runtime/phase-contributions.mjs +594 -0
- package/src/runtime/policy-workspace.mjs +170 -0
- package/src/runtime/prototype-iterations.mjs +235 -0
- package/src/runtime/provider-adapters.mjs +163 -0
- package/src/runtime/repository-index.mjs +838 -0
- package/src/runtime/runs.mjs +185 -0
- package/src/runtime/security-validation.mjs +1230 -0
- package/src/runtime/starter-materialisation.mjs +1155 -0
- package/src/runtime/team-hub-client.mjs +479 -0
- package/src/runtime/team-hub-database.mjs +288 -0
- package/src/runtime/team-hub-server.mjs +191 -0
- package/src/runtime/team-hub.mjs +110 -0
- package/src/runtime/tree-sitter-index.mjs +390 -0
- package/src/runtime/version.mjs +1 -0
- package/src/runtime/work.mjs +633 -0
- package/src/salesforce-source-map.mjs +212 -0
- package/src/security-validation-config.mjs +224 -0
- package/src/solution-readiness.mjs +620 -0
- package/src/starter-materialisation-contract.mjs +407 -0
- package/src/task-graph.mjs +544 -0
- package/src/team-hub-resources.mjs +239 -0
- package/src/team-hub.mjs +242 -0
- package/src/test-scenarios.mjs +622 -0
- package/src/validation-config.mjs +289 -0
- package/templates/SPECS/1.Scope/personas/registry.yaml +12 -0
- package/templates/SPECS/5.Strategy/patterns/context-packet.md +119 -0
- package/templates/SPECS/6.Build/_tracker-template.md +16 -0
- package/templates/SPECS/pipeline.yaml +62 -0
- package/templates/discovery-answers.yaml +86 -0
- package/templates/intent-body.md +21 -0
- package/tests/fixtures/context-benchmarks.json +9 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { ASSURANCE_NOTICE } from './security-validation-config.mjs';
|
|
2
|
+
import { listWorkItems } from './runtime/work.mjs';
|
|
3
|
+
import { selectContextualPersonas } from './runtime/persona-engagement.mjs';
|
|
4
|
+
import { designSystemStatus } from './design-systems.mjs';
|
|
5
|
+
|
|
6
|
+
const RECOMMENDATION_LIMIT = 8;
|
|
7
|
+
const PERSONA_LIMIT = 4;
|
|
8
|
+
const FOCUS_LIMIT = 500;
|
|
9
|
+
const BLOCKER_LIMIT = 3;
|
|
10
|
+
const MESSAGE_LIMIT = 240;
|
|
11
|
+
const CONTROL_CHARACTERS = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
|
|
12
|
+
const CONTINUE_ACTIONS = ['continueHarness', 'enterBuild', 'acquireTask', 'completeCurrentPhase'];
|
|
13
|
+
|
|
14
|
+
export const COMPANION_ADVISORY_NOTICE = 'Companion and persona guidance is advisory. It cannot create delivery permission, user evidence, specialist assurance or human acceptance.';
|
|
15
|
+
export const COMPANION_ASSURANCE_NOTICE = ASSURANCE_NOTICE;
|
|
16
|
+
|
|
17
|
+
function clean(value, limit = MESSAGE_LIMIT) {
|
|
18
|
+
return String(value ?? '').trim().slice(0, limit);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normaliseFocus(value) {
|
|
22
|
+
const focus = String(value ?? '').trim();
|
|
23
|
+
if (focus.length > FOCUS_LIMIT) throw new Error(`Companion focus must be ${FOCUS_LIMIT} characters or fewer.`);
|
|
24
|
+
if (CONTROL_CHARACTERS.test(focus)) throw new Error('Companion focus contains an unsafe control character.');
|
|
25
|
+
return focus;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function actionPermitted(item, name) {
|
|
29
|
+
return item?.execution?.actions?.[name]?.permitted === true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function classify(item) {
|
|
33
|
+
if (item?.lane === 'qa' || actionPermitted(item, 'approveBuild')) return 'human-decision';
|
|
34
|
+
if (CONTINUE_ACTIONS.some((name) => actionPermitted(item, name))) return 'continue';
|
|
35
|
+
if (actionPermitted(item, 'beginHarness')) return 'start';
|
|
36
|
+
return 'blocked';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function accountableRoute(item, recommendationClass) {
|
|
40
|
+
const searchable = `${item?.title ?? ''} ${item?.currentPhase ?? ''}`.toLowerCase();
|
|
41
|
+
if (item?.lane === 'qa') return 'Named Manual QA reviewer';
|
|
42
|
+
if (actionPermitted(item, 'approveBuild')) return 'Accountable project owner';
|
|
43
|
+
if (searchable.includes('security')) return 'Qualified security reviewer and risk owner';
|
|
44
|
+
if (recommendationClass === 'blocked') return 'Delivery owner for the first blocker';
|
|
45
|
+
return 'Existing governed delivery harness';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function recommendationReason(item, recommendationClass) {
|
|
49
|
+
if (item?.lane === 'qa') return 'The governed delivery has reached Manual QA and now needs accountable human judgement.';
|
|
50
|
+
if (actionPermitted(item, 'approveBuild')) return 'The plan is ready for an accountable human to decide whether Build may begin.';
|
|
51
|
+
if (recommendationClass === 'continue') return 'The governed execution actions show that this work can continue through its existing delivery harness.';
|
|
52
|
+
if (recommendationClass === 'start') return 'The governed execution actions show that this work can enter its delivery harness.';
|
|
53
|
+
return 'No governed start or continue action is currently permitted; review the bounded blockers before proceeding.';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function boundedBlockers(item) {
|
|
57
|
+
const candidates = [
|
|
58
|
+
...(Array.isArray(item?.execution?.blockers) ? item.execution.blockers : []),
|
|
59
|
+
...Object.values(item?.execution?.actions ?? {}).flatMap((action) => Array.isArray(action?.blockers) ? action.blockers : []),
|
|
60
|
+
];
|
|
61
|
+
const result = [];
|
|
62
|
+
const seen = new Set();
|
|
63
|
+
for (const blocker of candidates) {
|
|
64
|
+
const safe = {
|
|
65
|
+
code: clean(blocker?.code || 'blocked', 80),
|
|
66
|
+
message: clean(blocker?.message || blocker?.reason || 'A governed prerequisite remains incomplete.'),
|
|
67
|
+
};
|
|
68
|
+
const key = `${safe.code}:${safe.message}`;
|
|
69
|
+
if (!safe.message || seen.has(key)) continue;
|
|
70
|
+
seen.add(key);
|
|
71
|
+
result.push(safe);
|
|
72
|
+
if (result.length >= BLOCKER_LIMIT) break;
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function evidenceClasses(item, recommendationClass) {
|
|
78
|
+
const values = new Set(['governed-execution-state']);
|
|
79
|
+
if (item?.currentPhase) values.add('phase-progress');
|
|
80
|
+
if (item?.execution?.lifecycle?.status) values.add('delivery-lifecycle');
|
|
81
|
+
if (recommendationClass === 'human-decision') values.add('human-decision-record');
|
|
82
|
+
if (recommendationClass === 'blocked') values.add('bounded-blocker-evidence');
|
|
83
|
+
return [...values];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function handoff(item, recommendationClass) {
|
|
87
|
+
if (recommendationClass === 'start' && actionPermitted(item, 'beginHarness')) {
|
|
88
|
+
return { kind: 'begin', label: 'Begin governed delivery' };
|
|
89
|
+
}
|
|
90
|
+
if (recommendationClass === 'continue' && actionPermitted(item, 'continueHarness')) {
|
|
91
|
+
return { kind: 'continue', label: 'Continue governed delivery' };
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function safeRecommendation(item) {
|
|
97
|
+
const recommendationClass = classify(item);
|
|
98
|
+
return {
|
|
99
|
+
id: clean(item?.id || item?.intentId || item?.slug, 180),
|
|
100
|
+
slug: clean(item?.slug, 120),
|
|
101
|
+
domain: clean(item?.domain, 80),
|
|
102
|
+
title: clean(item?.title || item?.slug || 'Untitled work item', 180),
|
|
103
|
+
class: recommendationClass,
|
|
104
|
+
state: clean(item?.state, 80),
|
|
105
|
+
phase: clean(item?.currentPhase || item?.execution?.lifecycle?.currentPhase, 80),
|
|
106
|
+
progress: Number.isFinite(Number(item?.completionPercent)) ? Math.max(0, Math.min(100, Number(item.completionPercent))) : 0,
|
|
107
|
+
reason: recommendationReason(item, recommendationClass),
|
|
108
|
+
accountableRoute: accountableRoute(item, recommendationClass),
|
|
109
|
+
evidenceClasses: evidenceClasses(item, recommendationClass),
|
|
110
|
+
blockers: boundedBlockers(item),
|
|
111
|
+
handoff: handoff(item, recommendationClass),
|
|
112
|
+
priority: Number.isFinite(Number(item?.priority)) ? Number(item.priority) : 999,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function focusScore(recommendation, focus) {
|
|
117
|
+
if (!focus) return 0;
|
|
118
|
+
const value = focus.toLowerCase();
|
|
119
|
+
const exact = [recommendation.id, recommendation.slug, recommendation.title].map((candidate) => candidate.toLowerCase());
|
|
120
|
+
if (exact.includes(value)) return 10_000;
|
|
121
|
+
const haystack = `${recommendation.id} ${recommendation.title} ${recommendation.domain} ${recommendation.phase}`.toLowerCase();
|
|
122
|
+
if (haystack.includes(value)) return 5_000;
|
|
123
|
+
const tokens = value.split(/[^a-z0-9]+/).filter((token) => token.length >= 3);
|
|
124
|
+
return tokens.filter((token) => haystack.includes(token)).length * 100;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function classScore(value) {
|
|
128
|
+
return { 'human-decision': 400, continue: 300, start: 200, blocked: 100 }[value] ?? 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function rankedRecommendations(items, focus) {
|
|
132
|
+
return items.map(safeRecommendation)
|
|
133
|
+
.sort((left, right) => (
|
|
134
|
+
focusScore(right, focus) - focusScore(left, focus)
|
|
135
|
+
|| classScore(right.class) - classScore(left.class)
|
|
136
|
+
|| left.priority - right.priority
|
|
137
|
+
|| left.title.localeCompare(right.title)
|
|
138
|
+
|| left.id.localeCompare(right.id)
|
|
139
|
+
))
|
|
140
|
+
.slice(0, RECOMMENDATION_LIMIT)
|
|
141
|
+
.map(({ priority: _priority, ...recommendation }) => recommendation);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function personaAvailability(personas) {
|
|
145
|
+
const count = (tier) => personas.filter((persona) => persona?.tier === tier).length;
|
|
146
|
+
const premiumCount = count('premium');
|
|
147
|
+
return {
|
|
148
|
+
project: { installed: count('project') > 0, count: count('project') },
|
|
149
|
+
core: { installed: count('core') > 0, count: count('core') },
|
|
150
|
+
personal: { installed: count('personal') > 0, count: count('personal') },
|
|
151
|
+
premium: {
|
|
152
|
+
installed: premiumCount > 0,
|
|
153
|
+
count: premiumCount,
|
|
154
|
+
reason: premiumCount > 0
|
|
155
|
+
? 'Installed premium personas are available for optional enrichment.'
|
|
156
|
+
: 'No installed premium personas are available; the standard project and core baseline remains complete.',
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function personaSignals(spotlight, focus) {
|
|
162
|
+
if (!spotlight) return focus ? [focus] : [];
|
|
163
|
+
return [
|
|
164
|
+
focus,
|
|
165
|
+
spotlight.title,
|
|
166
|
+
spotlight.domain,
|
|
167
|
+
spotlight.class,
|
|
168
|
+
spotlight.phase,
|
|
169
|
+
spotlight.reason,
|
|
170
|
+
spotlight.accountableRoute,
|
|
171
|
+
...spotlight.evidenceClasses,
|
|
172
|
+
...spotlight.blockers.flatMap(({ code, message }) => [code, message]),
|
|
173
|
+
].filter(Boolean);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function reviewQuestions(spotlight, focus) {
|
|
177
|
+
if (!spotlight) {
|
|
178
|
+
return focus
|
|
179
|
+
? [`What project evidence would establish whether “${clean(focus, 120)}” belongs in the governed delivery queue?`]
|
|
180
|
+
: ['What outcome should be brought into the governed delivery queue next?'];
|
|
181
|
+
}
|
|
182
|
+
const questions = [];
|
|
183
|
+
const securityContext = `${spotlight.title} ${spotlight.phase} ${focus}`.toLowerCase().includes('security');
|
|
184
|
+
if (securityContext) {
|
|
185
|
+
questions.push('Who is the qualified human responsible for reviewing the security scope, findings, limitations and residual risk?');
|
|
186
|
+
}
|
|
187
|
+
if (spotlight.class === 'human-decision') {
|
|
188
|
+
questions.push(`Who holds the decision for ${spotlight.title}, and what evidence and limitations must they consider?`);
|
|
189
|
+
questions.push('What would make the accountable reviewer withhold approval or acceptance?');
|
|
190
|
+
} else if (spotlight.class === 'blocked') {
|
|
191
|
+
questions.push(`What evidence would allow the delivery owner to resolve the first blocker for ${spotlight.title}?`);
|
|
192
|
+
questions.push('Does recovery require a human decision, an external dependency, or a correction to the governed state?');
|
|
193
|
+
} else {
|
|
194
|
+
questions.push(`Does continuing ${spotlight.title} still serve the intended user outcome and current constraints?`);
|
|
195
|
+
questions.push('What evidence should be preserved at the next governed checkpoint?');
|
|
196
|
+
}
|
|
197
|
+
return [...new Set(questions)].slice(0, 3);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function readCompanionGuidance(projectRoot, options = {}) {
|
|
201
|
+
const focus = normaliseFocus(options.focus);
|
|
202
|
+
const items = Array.isArray(options.items) ? options.items : listWorkItems(projectRoot);
|
|
203
|
+
const personas = Array.isArray(options.personas) ? options.personas : [];
|
|
204
|
+
const recommendations = rankedRecommendations(items, focus);
|
|
205
|
+
const spotlight = recommendations[0] ?? null;
|
|
206
|
+
const activePersonas = selectContextualPersonas({
|
|
207
|
+
personaCatalogue: personas,
|
|
208
|
+
signals: personaSignals(spotlight, focus),
|
|
209
|
+
context: { focus, spotlight },
|
|
210
|
+
contextLabel: spotlight?.title || focus || 'project delivery',
|
|
211
|
+
limit: PERSONA_LIMIT,
|
|
212
|
+
});
|
|
213
|
+
const summary = { total: recommendations.length, humanDecision: 0, continue: 0, start: 0, blocked: 0 };
|
|
214
|
+
for (const recommendation of recommendations) {
|
|
215
|
+
if (recommendation.class === 'human-decision') summary.humanDecision += 1;
|
|
216
|
+
else summary[recommendation.class] += 1;
|
|
217
|
+
}
|
|
218
|
+
let designSystem;
|
|
219
|
+
try {
|
|
220
|
+
const status = designSystemStatus(projectRoot, options.designSystems ?? {});
|
|
221
|
+
designSystem = {
|
|
222
|
+
schema: status.schema,
|
|
223
|
+
status: status.status,
|
|
224
|
+
mode: status.mode,
|
|
225
|
+
approved: status.approved,
|
|
226
|
+
root: { id: status.root.id, name: status.root.name, version: status.root.version, sourceClass: status.root.sourceClass, digest: status.root.digest },
|
|
227
|
+
effectiveDigest: status.effectiveDigest,
|
|
228
|
+
approvedBy: status.approvedBy,
|
|
229
|
+
approvedAt: status.approvedAt,
|
|
230
|
+
evidence: status.evidence,
|
|
231
|
+
organisationRecommendations: status.organisationRecommendations,
|
|
232
|
+
organisationRecommendationStatus: status.organisationRecommendationStatus,
|
|
233
|
+
notice: status.notice,
|
|
234
|
+
};
|
|
235
|
+
} catch {
|
|
236
|
+
designSystem = { schema: 'ewai.design-system-status/v1', status: 'unavailable', mode: 'unknown', approved: false, notice: 'Project design-system status is unavailable.' };
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
schema: 'ewai.companion-guidance/v1',
|
|
240
|
+
status: recommendations.length ? 'ready' : 'empty',
|
|
241
|
+
focus,
|
|
242
|
+
summary,
|
|
243
|
+
recommendations,
|
|
244
|
+
spotlight,
|
|
245
|
+
activePersonas,
|
|
246
|
+
personaAvailability: personaAvailability(personas),
|
|
247
|
+
designSystem,
|
|
248
|
+
baseline: {
|
|
249
|
+
standardModel: true,
|
|
250
|
+
complete: true,
|
|
251
|
+
description: 'The host model with installed project and core personas provides the complete baseline. Installed premium and personal personas can add optional specialist perspectives.',
|
|
252
|
+
},
|
|
253
|
+
review: { advisory: true, questions: reviewQuestions(spotlight, focus) },
|
|
254
|
+
notices: { advisory: COMPANION_ADVISORY_NOTICE, security: COMPANION_ASSURANCE_NOTICE },
|
|
255
|
+
limits: { recommendations: RECOMMENDATION_LIMIT, personas: PERSONA_LIMIT, focusCharacters: FOCUS_LIMIT },
|
|
256
|
+
};
|
|
257
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { DEFAULT_PREMIUM_UPGRADE_URL } from './persona-entitlements.mjs';
|
|
2
|
+
|
|
3
|
+
export function premiumPersonaLearningAction() {
|
|
4
|
+
return {
|
|
5
|
+
id: 7,
|
|
6
|
+
key: 'premium-personas',
|
|
7
|
+
label: 'Read about premium personas',
|
|
8
|
+
url: DEFAULT_PREMIUM_UPGRADE_URL,
|
|
9
|
+
authority: 'none'
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const baseActions = [
|
|
14
|
+
{ id: 1, key: 'capture', label: 'Capture something new' },
|
|
15
|
+
{ id: 2, key: 'movable-intent', label: 'Pick up an intent that can move forward' },
|
|
16
|
+
{ id: 3, key: 'explore', label: 'Explore an idea and shape connected intents' },
|
|
17
|
+
{ id: 4, key: 'knowledge', label: 'Explore the Mind Palace, risks, or standards' },
|
|
18
|
+
{ id: 5, key: 'recommendations', label: 'See recommendations and possible next work' }
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export function companionOpening(options = {}) {
|
|
22
|
+
const actions = [...baseActions];
|
|
23
|
+
if (options.hasWork) {
|
|
24
|
+
actions.push({ id: 6, key: 'continue', label: 'Continue a piece of work' });
|
|
25
|
+
}
|
|
26
|
+
if(options.premiumActive!==true){
|
|
27
|
+
actions.push(premiumPersonaLearningAction());
|
|
28
|
+
actions.push({ id: 8, key: 'premium-persona-setup', label: 'Set up premium personas', command: 'ewai persona premium configure --project .', authority: 'verify-and-install-consent-required' });
|
|
29
|
+
}
|
|
30
|
+
actions.push({id:9,key:'dashboard-configuration',label:'Configure the dashboard',skill:'ewai-dashboard-configuration',command:'ewai dashboard preferences --project .',authority:'preference-save-consent-required'});
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
schema: 'ewai.companion-opening/v1',
|
|
34
|
+
presentation: 'mandatory-numbered-menu',
|
|
35
|
+
heading: 'What would you like to do?',
|
|
36
|
+
output: { routineStatusLines: 4, progressMaxWords: 24, errorMaxWords: 60,
|
|
37
|
+
details: 'on-request', preserveWarnings: true, repeatMenu: 'only-at-next-decision' },
|
|
38
|
+
actions,
|
|
39
|
+
personaSetup: options.licenceNotConfigured === true && options.premiumActive!==true ? {
|
|
40
|
+
question: 'Do you have a premium persona licence, or shall we use the core personas?',
|
|
41
|
+
choices: [
|
|
42
|
+
{ key: 'configure', label: 'Set up a licence privately', command: 'ewai persona premium configure --project .' },
|
|
43
|
+
{ key: 'read', label: 'Read about personas first', url: DEFAULT_PREMIUM_UPGRADE_URL },
|
|
44
|
+
{ key: 'core', label: 'Keep using the core personas' }
|
|
45
|
+
]
|
|
46
|
+
} : null,
|
|
47
|
+
spotlight: options.spotlight ?? null,
|
|
48
|
+
activePersonas: options.activePersonas ?? [],
|
|
49
|
+
notices: options.notices ?? null,
|
|
50
|
+
closingPrompt: "What's on your mind?",
|
|
51
|
+
contract: [
|
|
52
|
+
'Report the check-in status before the menu in four routine lines; retain blockers, warnings and unknown checks with short reasons. Detailed diagnostics are on request.',
|
|
53
|
+
'Do not repeat the menu while carrying out a selected action. Give at most one short progress sentence per meaningful checkpoint; no shell/file/JSON narration or running commentary about reasoning.',
|
|
54
|
+
'Render every action in order as `[id] label`.',
|
|
55
|
+
'Action [7] is optional learning only; reading cannot activate, purchase or download personas.',
|
|
56
|
+
'Do not replace the menu with narrative, recommendations, or a generic question.',
|
|
57
|
+
'When spotlight is present, render its bounded advisory context and active persona names and tiers after the numbered actions.',
|
|
58
|
+
'When personaSetup is present, ask its exact question after the menu and before closingPrompt. Respect a decline. Action [8] uses the guarded dashboard password form by default; a private terminal prompt is an alternative only when a genuine interactive terminal is available. Explain that key submission verifies and immediately installs the pack. Confirm installed readiness before chosen Archaeology. Reading and session checks never download.',
|
|
59
|
+
'End the opening with the exact closingPrompt.'
|
|
60
|
+
]
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { basename, resolve } from 'node:path';
|
|
4
|
+
import { createInterface } from 'node:readline/promises';
|
|
5
|
+
import { install } from './install.mjs';
|
|
6
|
+
import { detectExistingCodebase } from './project.mjs';
|
|
7
|
+
import { projectPaths, resolveProjectRoot } from './paths.mjs';
|
|
8
|
+
import { dashboardHandoffStatus } from './runtime/dashboard-handoffs.mjs';
|
|
9
|
+
|
|
10
|
+
const HOSTS = ['claude', 'codex', 'antigravity'];
|
|
11
|
+
const HOST_LABELS = {
|
|
12
|
+
claude: 'Claude Code',
|
|
13
|
+
codex: 'Codex',
|
|
14
|
+
antigravity: 'Google Antigravity'
|
|
15
|
+
};
|
|
16
|
+
const HOST_COMMANDS = {
|
|
17
|
+
claude: 'claude',
|
|
18
|
+
codex: 'codex',
|
|
19
|
+
antigravity: 'agy',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function projectState(start) {
|
|
23
|
+
let projectRoot;
|
|
24
|
+
try {
|
|
25
|
+
projectRoot = resolveProjectRoot(start);
|
|
26
|
+
} catch {
|
|
27
|
+
projectRoot = resolve(start);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const initialized = existsSync(projectPaths(projectRoot).configPath);
|
|
31
|
+
const existingCodebase = detectExistingCodebase(projectRoot).detected;
|
|
32
|
+
const handoffState = initialized
|
|
33
|
+
? dashboardHandoffStatus(projectRoot)
|
|
34
|
+
: { status: 'not-initialized', handoffs: [], error: null };
|
|
35
|
+
return {
|
|
36
|
+
projectRoot,
|
|
37
|
+
initialized,
|
|
38
|
+
existingCodebase,
|
|
39
|
+
dashboardHandoff: handoffState.handoffs[0] ?? null,
|
|
40
|
+
dashboardHandoffError: handoffState.error,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function companionStartupPlan(state) {
|
|
45
|
+
return { schema: 'ewai.companion-startup/v1', mode: state.initialized ? 'session' : 'onboarding',
|
|
46
|
+
steps: state.initialized ? ['check-in', 'opening', 'selected-action'] : [
|
|
47
|
+
'location-agreement', 'initialise', 'check-in', 'premium-personas', 'human-briefing', 'context-import',
|
|
48
|
+
...(state.existingCodebase ? ['offer-archaeology'] : []),
|
|
49
|
+
'discovery', 'doctor', 'opening'
|
|
50
|
+
],
|
|
51
|
+
archaeology: !state.initialized && state.existingCodebase ? { optional: true,
|
|
52
|
+
question: 'Would you like us to analyse the existing code and reconstruct its missing project documentation?',
|
|
53
|
+
onAccept: ['archaeology', 'review-and-curate'], onDecline: 'discovery' } : null,
|
|
54
|
+
flexibility: ['agreed knowledge location', 'optional context import', 'optional premium access', 'optional archaeology', 'human-owned deferral'],
|
|
55
|
+
safeguards: ['briefing before deep analysis', 'explicit download consent', 'review before promotion', 'complete delivery gates'] };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function buildCompanionPrompt(state) {
|
|
59
|
+
const contributionReview = state.dashboardHandoff?.action === 'review-contribution';
|
|
60
|
+
const startup = companionStartupPlan(state);
|
|
61
|
+
return [
|
|
62
|
+
'Use $ewai-pipeline to open the EWAI companion for this project.',
|
|
63
|
+
'Follow its companion opening contract completely and keep setup inside this AI conversation.',
|
|
64
|
+
`Project root: ${state.projectRoot}`,
|
|
65
|
+
`EWAI initialized: ${state.initialized ? 'yes' : 'no'}`,
|
|
66
|
+
`Existing codebase detected: ${state.existingCodebase ? 'yes' : 'no'}`,
|
|
67
|
+
`EWAI orchestrator: ${state.orchestrator ?? 'manual'}`,
|
|
68
|
+
state.dashboardHandoff
|
|
69
|
+
? `Dashboard handoff: ${state.dashboardHandoff.action} ${state.dashboardHandoff.intentId} at ${state.dashboardHandoff.phase || 'the next verified phase'} (handoff ${state.dashboardHandoff.id}; authority ${state.dashboardHandoff.authority ?? (contributionReview ? 'none' : 'guarded')})`
|
|
70
|
+
: state.dashboardHandoffError ? `Dashboard handoff: unavailable (${state.dashboardHandoffError})` : 'Dashboard handoff: none',
|
|
71
|
+
'Do not ask the user to copy or run CLI commands.',
|
|
72
|
+
`KNOWN STARTUP PATH: ${startup.steps.join(' -> ')}. Follow it without dumping the checklist to the user. Preserve agreed knowledge location, optional persona access/context import and human-owned deferral; do not silently skip a checkpoint.`,
|
|
73
|
+
'- Premium personas: inspect access and the installed index, load available installed lenses, and settle chosen private setup or explicitly approved installation before deeper analysis. A session check never downloads implicitly. For existing code, always offer optional Archaeology after human briefing and optional context import; explain its value and wait for acceptance. Only if accepted, run Archaeology and review-and-curate its proposals before confirmed Discovery and Doctor. If declined, continue to Discovery and Doctor without reconstruction; Archaeology remains available later. New code follows briefing/context then Discovery. Show the early dashboard link while onboarding continues; folder readiness is not completed analysis.',
|
|
74
|
+
state.initialized
|
|
75
|
+
? 'STARTUP ROUTE: run one check-in for the resolved project, or reuse this launch’s managed hook result. Do not reinitialise or repeat first-run onboarding.'
|
|
76
|
+
: 'INITIALISATION BEFORE CHECK-IN: ask one location question recommending SPECS in this folder for a single repository/monorepo; for independent repositories recommend a dedicated knowledge repository. After agreement run ewai init --project <resolved-root> --json with the agreed --specs path, and --init-specs-repo only when approved. Do not investigate EWAI before running init. Do not show invented check-in status or a session menu before initialisation.',
|
|
77
|
+
'Do not inspect the installed EWAI source during routine startup. Do not run ewai --version: version/update status comes from check-in. Use the CLI and returned contracts; investigate implementation only when a specific unresolved failure needs diagnosis.',
|
|
78
|
+
'Use $ewai-persona-entitlement only when setup is chosen. The guarded dashboard password form is the default; a private terminal prompt is an alternative only when a genuine interactive terminal is available. Never ask for the licence key in chat or arguments. Submission verifies and installs immediately; confirm the installed version and refresh the persona index before using the pack.',
|
|
79
|
+
'- After successful first initialisation, offer the returned onboarding.personaLearning question before deeper analysis or Archaeology, for both new and existing code. Respect a decline, retain the human briefing and purpose alignment, and do not repeat first-run onboarding in an already initialised session.',
|
|
80
|
+
'Never use the host AI generic plan mode for intent delivery; use $ewai-deliver and its guarded resume state.',
|
|
81
|
+
'MANDATORY FIRST-RESPONSE PRESENTATION CONTRACT:',
|
|
82
|
+
'- For a fresh folder, the first response is the single location question, not the session menu. After agreement initialise promptly, show compact check-in readiness, then follow the known onboarding path one decision at a time. Keep the human project briefing and purpose alignment; do not replace them with inferred code behaviour. The normal session menu follows onboarding or an explicit human-owned deferral.',
|
|
83
|
+
'- Keep the session status to four routine lines, followed by the returned menu and one question. Preserve warnings, blockers, unknown checks and mandatory standards; expand technical details only on request. Routine progress is one sentence of at most 24 words per meaningful checkpoint. Do not narrate commands, file reads, JSON parsing, internal policies or reasoning. Do not repeat the menu during a selected action.',
|
|
84
|
+
'- A narrative-only opening is invalid.',
|
|
85
|
+
'- After check-in status, render the heading and every numbered action returned in checkin.companion, in order.',
|
|
86
|
+
'- Render each action exactly as `[id] label`; do not replace the menu with prose or recommendations.',
|
|
87
|
+
'- When any intent or delivery state exists, the returned menu includes `[6] Continue a piece of work`; never omit it.',
|
|
88
|
+
'- Include `[7] Read about premium personas` and `[8] Set up premium personas` only when returned by check-in. Both are absent when premium access is available and the installed pack is verified. Learning opens https://www.conversationalcoding.dev/personas/ without purchasing, activating or downloading content.',
|
|
89
|
+
'- Chosen premium setup verifies and installs on explicit private key submission. Resolve it and refresh installed persona routing before Archaeology; after failure offer retry or explicit core-only continuation, preserving briefing and purpose alignment. Licence management remains available in dashboard Configuration.',
|
|
90
|
+
'- Always render the returned `[9] Configure the dashboard` action. Use $ewai-dashboard-configuration for explicit project-local view choices; hiding views never disables required checks or approvals.',
|
|
91
|
+
'- When checkin.companion.personaSetup is present, ask its exact optional setup question after the menu and before the closing prompt. Offer private setup, reading first, or core use; do not invent this question for expired, invalid or unavailable configured licences.',
|
|
92
|
+
contributionReview
|
|
93
|
+
? '- A contribution-review handoff is pending. Identify it immediately after the numbered menu. Treat “go” as confirmation to claim it, use $ewai-phase-evidence, preserve authority none, and mark it completed only after advisory feedback is returned. Do not invoke $ewai-deliver or mutate delivery state for this review.'
|
|
94
|
+
: '- If a delivery dashboard handoff is pending, identify that intent immediately after the numbered menu. Treat “go” as selecting it and confirmation to claim it, then use $ewai-deliver; never substitute the host generic plan mode.',
|
|
95
|
+
'- End the opening with the exact line `What\'s on your mind?`.',
|
|
96
|
+
state.initialized
|
|
97
|
+
? '- Do not finish the first response until both check-in status and the numbered menu are visible.'
|
|
98
|
+
: '- After initialisation show concise readiness and continue onboarding. The numbered session-opening contract applies once onboarding is complete or explicitly deferred by the owner. Offer setup/learning without duplicating an already answered question.'
|
|
99
|
+
].join('\n');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function hostInvocation(host, prompt) {
|
|
103
|
+
if (host === 'claude') return { command: 'claude', args: [prompt] };
|
|
104
|
+
if (host === 'codex') return { command: 'codex', args: [prompt] };
|
|
105
|
+
if (host === 'antigravity') return { command: 'agy', args: ['--prompt-interactive', prompt] };
|
|
106
|
+
throw new Error(`Unsupported EWAI agent host: ${host}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function probeHost(host, env) {
|
|
110
|
+
const command = HOST_COMMANDS[host];
|
|
111
|
+
const result = spawnSync(command, ['--version'], { env, stdio: 'ignore', timeout: 3000 });
|
|
112
|
+
if (!result.error && result.status === 0) return { available: true, reason: '' };
|
|
113
|
+
if (result.error?.code === 'ENOENT') {
|
|
114
|
+
return { available: false, reason: `\`${command}\` command not found on this terminal PATH` };
|
|
115
|
+
}
|
|
116
|
+
if (result.error?.code === 'ETIMEDOUT') {
|
|
117
|
+
return { available: false, reason: `\`${command} --version\` timed out` };
|
|
118
|
+
}
|
|
119
|
+
return { available: false, reason: `\`${command} --version\` did not complete successfully` };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function detectHostStatuses(options = {}) {
|
|
123
|
+
const env = options.env ?? process.env;
|
|
124
|
+
const probe = options.probe ?? ((host) => probeHost(host, env));
|
|
125
|
+
return (options.hosts ?? HOSTS).map((host) => {
|
|
126
|
+
const result = probe(host);
|
|
127
|
+
const normalized = typeof result === 'boolean'
|
|
128
|
+
? { available: result, reason: result ? '' : `\`${host}\` command is not available in this terminal` }
|
|
129
|
+
: result;
|
|
130
|
+
return {
|
|
131
|
+
host,
|
|
132
|
+
label: HOST_LABELS[host],
|
|
133
|
+
available: normalized.available === true,
|
|
134
|
+
reason: normalized.reason ?? ''
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function detectAvailableHosts(options = {}) {
|
|
140
|
+
return detectHostStatuses(options).filter((item) => item.available).map((item) => item.host);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function formatHostAvailability(statuses) {
|
|
144
|
+
const available = statuses.filter((item) => item.available);
|
|
145
|
+
const unavailable = statuses.filter((item) => !item.available);
|
|
146
|
+
const lines = ['AI hosts available to EWAI:'];
|
|
147
|
+
if (available.length) {
|
|
148
|
+
available.forEach((item, index) => lines.push(` ${index + 1}. ${item.label}`));
|
|
149
|
+
} else {
|
|
150
|
+
lines.push(' None');
|
|
151
|
+
}
|
|
152
|
+
if (unavailable.length) {
|
|
153
|
+
lines.push('', 'Not available in this terminal:');
|
|
154
|
+
unavailable.forEach((item) => lines.push(` • ${item.label} — ${item.reason}`));
|
|
155
|
+
}
|
|
156
|
+
return lines.join('\n');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function selectAvailableHost(available, preferred) {
|
|
160
|
+
if (preferred && available.includes(preferred)) return preferred;
|
|
161
|
+
return available.length === 1 ? available[0] : null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function refreshHostSkills(host, options = {}) {
|
|
165
|
+
return install({
|
|
166
|
+
scope: 'global',
|
|
167
|
+
host,
|
|
168
|
+
home: options.home,
|
|
169
|
+
mode: 'copy',
|
|
170
|
+
force: true,
|
|
171
|
+
installBin: false
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function askForHost(available, io) {
|
|
176
|
+
if (!io.input.isTTY || !io.output.isTTY) return null;
|
|
177
|
+
const prompt = createInterface({ input: io.input, output: io.output });
|
|
178
|
+
try {
|
|
179
|
+
io.output.write('\nChoose the AI you want EWAI to open.\n');
|
|
180
|
+
while (true) {
|
|
181
|
+
const answer = (await prompt.question('Host: ')).trim();
|
|
182
|
+
const index = Number.parseInt(answer, 10) - 1;
|
|
183
|
+
if (available[index]) return available[index];
|
|
184
|
+
io.output.write(`Choose 1-${available.length}.\n`);
|
|
185
|
+
}
|
|
186
|
+
} finally {
|
|
187
|
+
prompt.close();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function spawnInteractive(invocation, options) {
|
|
192
|
+
return new Promise((resolvePromise, reject) => {
|
|
193
|
+
const child = spawn(invocation.command, invocation.args, {
|
|
194
|
+
cwd: options.cwd,
|
|
195
|
+
env: options.env,
|
|
196
|
+
stdio: 'inherit'
|
|
197
|
+
});
|
|
198
|
+
child.once('error', reject);
|
|
199
|
+
child.once('exit', (code, signal) => {
|
|
200
|
+
if (signal) reject(new Error(`${invocation.command} exited after signal ${signal}`));
|
|
201
|
+
else resolvePromise(code ?? 1);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export async function runCompanion(options = {}) {
|
|
207
|
+
const input = options.input ?? process.stdin;
|
|
208
|
+
const output = options.output ?? process.stdout;
|
|
209
|
+
const errorOutput = options.errorOutput ?? process.stderr;
|
|
210
|
+
const env = options.env ?? process.env;
|
|
211
|
+
const state = projectState(options.cwd ?? process.cwd());
|
|
212
|
+
const preferredValue = String(options.preferredHost ?? env.EWAI_HOST ?? '').toLowerCase();
|
|
213
|
+
const preferred = preferredValue === 'agy' ? 'antigravity' : preferredValue;
|
|
214
|
+
const preferredStatuses = HOSTS.includes(preferred)
|
|
215
|
+
? detectHostStatuses({ env, probe: options.probeHost, hosts: [preferred] }) : [];
|
|
216
|
+
const statuses = preferredStatuses[0]?.available ? preferredStatuses
|
|
217
|
+
: detectHostStatuses({ env, probe: options.probeHost });
|
|
218
|
+
const available = statuses.filter((item) => item.available).map((item) => item.host);
|
|
219
|
+
if (options.verbose || !available.length) output.write(`${formatHostAvailability(statuses)}\n`);
|
|
220
|
+
|
|
221
|
+
if (!available.length) {
|
|
222
|
+
errorOutput.write([
|
|
223
|
+
'EWAI cannot open an AI conversation because no supported CLI is available.',
|
|
224
|
+
'Install or activate one supported AI CLI, then run `ewai` again.',
|
|
225
|
+
'Use `ewai --help` only for automation and diagnostic commands.',
|
|
226
|
+
''
|
|
227
|
+
].join('\n'));
|
|
228
|
+
return 1;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let host = selectAvailableHost(available, preferred);
|
|
232
|
+
if (!host && available.length > 1) {
|
|
233
|
+
if (!options.verbose) output.write(`${formatHostAvailability(statuses)}\n`);
|
|
234
|
+
host = await askForHost(available, { input, output });
|
|
235
|
+
}
|
|
236
|
+
if (!host) {
|
|
237
|
+
errorOutput.write(`EWAI found ${available.map((item) => HOST_LABELS[item]).join(', ')}, but cannot choose interactively in this session. Set EWAI_HOST to claude, codex, or antigravity.\n`);
|
|
238
|
+
return 1;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const prepareHost = options.prepareHost ?? ((selectedHost) => refreshHostSkills(selectedHost));
|
|
242
|
+
try {
|
|
243
|
+
await prepareHost(host);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
errorOutput.write(`EWAI could not prepare its companion skills for ${HOST_LABELS[host]}: ${error.message}\n`);
|
|
246
|
+
errorOutput.write('Check that your user account can write to the AI host skill directory, then run `ewai` again.\n');
|
|
247
|
+
return 1;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
output.write(`EWAI · ${basename(state.projectRoot)} → ${HOST_LABELS[host]}\n`);
|
|
251
|
+
const orchestratedState = { ...state, orchestrator: host, startup: companionStartupPlan(state) };
|
|
252
|
+
const invocation = hostInvocation(host, buildCompanionPrompt(orchestratedState));
|
|
253
|
+
const hostEnv = { ...env, EWAI_ORCHESTRATOR: host };
|
|
254
|
+
const launch = options.spawnHost ?? ((value) => spawnInteractive(value, { cwd: state.projectRoot, env: hostEnv }));
|
|
255
|
+
return launch(invocation, orchestratedState);
|
|
256
|
+
}
|