@stravica-ai/digital-operator-pa 0.1.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/LICENSE +21 -0
- package/README.md +170 -0
- package/bin/desk +9 -0
- package/bin/distill +2 -0
- package/bin/estate-sync +2 -0
- package/bin/launch +2 -0
- package/bin/memory-sync +2 -0
- package/bin/migrate-inspect +2 -0
- package/bin/pa +2 -0
- package/bin/pa-coordinator-hook +2 -0
- package/bin/pa-init +2 -0
- package/bin/research +2 -0
- package/bin/scheduler +2 -0
- package/bin/secrets +2 -0
- package/bin/slice +2 -0
- package/bin/smoke +9 -0
- package/bin/sync +2 -0
- package/dist/engineering-pa-cli/README.md +205 -0
- package/dist/engineering-pa-cli/bin/estate-sync +2 -0
- package/dist/engineering-pa-cli/bin/launch +2 -0
- package/dist/engineering-pa-cli/bin/pa +2 -0
- package/dist/engineering-pa-cli/bin/pa-coordinator-hook +12 -0
- package/dist/engineering-pa-cli/bin/research +2 -0
- package/dist/engineering-pa-cli/bin/scheduler +2 -0
- package/dist/engineering-pa-cli/bin/secrets +2 -0
- package/dist/engineering-pa-cli/bin/smoke +9 -0
- package/dist/engineering-pa-cli/bin/sync +2 -0
- package/dist/engineering-pa-cli/config/coordinator-allowlist.json +118 -0
- package/dist/engineering-pa-cli/docs/coordinator-hook.md +211 -0
- package/dist/engineering-pa-cli/docs/estate-sync.md +412 -0
- package/dist/engineering-pa-cli/docs/gsc.md +175 -0
- package/dist/engineering-pa-cli/docs/lint-linkedin.md +139 -0
- package/dist/engineering-pa-cli/docs/secrets.md +221 -0
- package/dist/engineering-pa-cli/docs/smoke.md +113 -0
- package/dist/engineering-pa-cli/docs/staging-alias.md +114 -0
- package/dist/engineering-pa-cli/index.mjs +17 -0
- package/dist/engineering-pa-cli/package.json +37 -0
- package/dist/engineering-pa-cli/src/autonomy/cli.mjs +329 -0
- package/dist/engineering-pa-cli/src/autonomy/envelope.mjs +301 -0
- package/dist/engineering-pa-cli/src/coordinator-hook/cli.mjs +140 -0
- package/dist/engineering-pa-cli/src/coordinator-hook/config.mjs +134 -0
- package/dist/engineering-pa-cli/src/coordinator-hook/message.mjs +47 -0
- package/dist/engineering-pa-cli/src/coordinator-hook/policy.mjs +211 -0
- package/dist/engineering-pa-cli/src/coordinator-hook/repo-root.mjs +44 -0
- package/dist/engineering-pa-cli/src/coordinator-hook/self.mjs +30 -0
- package/dist/engineering-pa-cli/src/environment/cli.mjs +287 -0
- package/dist/engineering-pa-cli/src/environment/probes.mjs +302 -0
- package/dist/engineering-pa-cli/src/gsc/auth.mjs +148 -0
- package/dist/engineering-pa-cli/src/gsc/cli.mjs +93 -0
- package/dist/engineering-pa-cli/src/gsc/cloudflare.mjs +318 -0
- package/dist/engineering-pa-cli/src/gsc/commands/add-property.mjs +70 -0
- package/dist/engineering-pa-cli/src/gsc/commands/list-properties.mjs +33 -0
- package/dist/engineering-pa-cli/src/gsc/commands/pull-analytics.mjs +130 -0
- package/dist/engineering-pa-cli/src/gsc/commands/submit-sitemap.mjs +41 -0
- package/dist/engineering-pa-cli/src/gsc/commands/verify-via-cf-dns.mjs +189 -0
- package/dist/engineering-pa-cli/src/knowledge/call-log.mjs +59 -0
- package/dist/engineering-pa-cli/src/knowledge/cli.mjs +242 -0
- package/dist/engineering-pa-cli/src/knowledge/eval.mjs +446 -0
- package/dist/engineering-pa-cli/src/knowledge/formatters.mjs +331 -0
- package/dist/engineering-pa-cli/src/knowledge/graph.mjs +710 -0
- package/dist/engineering-pa-cli/src/launch/README.md +214 -0
- package/dist/engineering-pa-cli/src/launch/cli.mjs +51 -0
- package/dist/engineering-pa-cli/src/launch/commands/launch.mjs +106 -0
- package/dist/engineering-pa-cli/src/launch/identity.mjs +43 -0
- package/dist/engineering-pa-cli/src/launch/planner.mjs +44 -0
- package/dist/engineering-pa-cli/src/launch/repo-root.mjs +53 -0
- package/dist/engineering-pa-cli/src/launch/runner.mjs +147 -0
- package/dist/engineering-pa-cli/src/launch/task-loader.mjs +85 -0
- package/dist/engineering-pa-cli/src/lint/cli.mjs +61 -0
- package/dist/engineering-pa-cli/src/lint/commands/linkedin.mjs +133 -0
- package/dist/engineering-pa-cli/src/lint/detectors.mjs +90 -0
- package/dist/engineering-pa-cli/src/lint/linter.mjs +69 -0
- package/dist/engineering-pa-cli/src/lint/reporter.mjs +32 -0
- package/dist/engineering-pa-cli/src/lint/rules.mjs +132 -0
- package/dist/engineering-pa-cli/src/pa/cli.mjs +369 -0
- package/dist/engineering-pa-cli/src/pa/doc-groups.mjs +164 -0
- package/dist/engineering-pa-cli/src/pa/io.mjs +80 -0
- package/dist/engineering-pa-cli/src/pa/project-cli.mjs +385 -0
- package/dist/engineering-pa-cli/src/pa/projects.mjs +352 -0
- package/dist/engineering-pa-cli/src/pa/repo-root.mjs +64 -0
- package/dist/engineering-pa-cli/src/pa/session.mjs +254 -0
- package/dist/engineering-pa-cli/src/pa/work-cli.mjs +164 -0
- package/dist/engineering-pa-cli/src/pa/work.mjs +196 -0
- package/dist/engineering-pa-cli/src/pa/workstreams.mjs +201 -0
- package/dist/engineering-pa-cli/src/research/README.md +360 -0
- package/dist/engineering-pa-cli/src/research/briefs/extractor.md +103 -0
- package/dist/engineering-pa-cli/src/research/briefs/searcher.md +113 -0
- package/dist/engineering-pa-cli/src/research/briefs/synthesizer.md +124 -0
- package/dist/engineering-pa-cli/src/research/briefs/verifier.md +86 -0
- package/dist/engineering-pa-cli/src/research/cli.mjs +141 -0
- package/dist/engineering-pa-cli/src/research/commands/gates.mjs +176 -0
- package/dist/engineering-pa-cli/src/research/commands/list.mjs +56 -0
- package/dist/engineering-pa-cli/src/research/commands/mark.mjs +97 -0
- package/dist/engineering-pa-cli/src/research/commands/paths.mjs +69 -0
- package/dist/engineering-pa-cli/src/research/commands/prune.mjs +86 -0
- package/dist/engineering-pa-cli/src/research/commands/run.mjs +94 -0
- package/dist/engineering-pa-cli/src/research/commands/show.mjs +43 -0
- package/dist/engineering-pa-cli/src/research/docs/operator-present-flow.md +120 -0
- package/dist/engineering-pa-cli/src/research/format.mjs +59 -0
- package/dist/engineering-pa-cli/src/research/promote.mjs +260 -0
- package/dist/engineering-pa-cli/src/research/schema/finding.schema.json +55 -0
- package/dist/engineering-pa-cli/src/research/schema/gates.schema.json +34 -0
- package/dist/engineering-pa-cli/src/research/schema/manifest.schema.json +59 -0
- package/dist/engineering-pa-cli/src/research/schema/plan.schema.json +41 -0
- package/dist/engineering-pa-cli/src/research/schema/sources.schema.json +31 -0
- package/dist/engineering-pa-cli/src/research/store/findings.mjs +61 -0
- package/dist/engineering-pa-cli/src/research/store/gates.mjs +73 -0
- package/dist/engineering-pa-cli/src/research/store/index.mjs +312 -0
- package/dist/engineering-pa-cli/src/research/store/manifest.mjs +56 -0
- package/dist/engineering-pa-cli/src/research/store/plan.mjs +59 -0
- package/dist/engineering-pa-cli/src/research/store/sources.mjs +44 -0
- package/dist/engineering-pa-cli/src/research/store/validate.mjs +92 -0
- package/dist/engineering-pa-cli/src/research/workflows/research-normal.js +573 -0
- package/dist/engineering-pa-cli/src/scheduler/cli.mjs +197 -0
- package/dist/engineering-pa-cli/src/secrets/cli.mjs +254 -0
- package/dist/engineering-pa-cli/src/secrets/commands/backup-template.mjs +67 -0
- package/dist/engineering-pa-cli/src/secrets/commands/backup.mjs +85 -0
- package/dist/engineering-pa-cli/src/secrets/commands/delete.mjs +58 -0
- package/dist/engineering-pa-cli/src/secrets/commands/find.mjs +36 -0
- package/dist/engineering-pa-cli/src/secrets/commands/get.mjs +49 -0
- package/dist/engineering-pa-cli/src/secrets/commands/init.mjs +69 -0
- package/dist/engineering-pa-cli/src/secrets/commands/list.mjs +37 -0
- package/dist/engineering-pa-cli/src/secrets/commands/rotate.mjs +46 -0
- package/dist/engineering-pa-cli/src/secrets/commands/set.mjs +123 -0
- package/dist/engineering-pa-cli/src/secrets/commands/status.mjs +146 -0
- package/dist/engineering-pa-cli/src/secrets/commands/vaults.mjs +57 -0
- package/dist/engineering-pa-cli/src/secrets/commands/verify.mjs +143 -0
- package/dist/engineering-pa-cli/src/secrets/vault.mjs +766 -0
- package/dist/engineering-pa-cli/src/smoke/checks.mjs +199 -0
- package/dist/engineering-pa-cli/src/smoke/cli.mjs +108 -0
- package/dist/engineering-pa-cli/src/smoke/commands/run.mjs +237 -0
- package/dist/engineering-pa-cli/src/smoke/runner.mjs +191 -0
- package/dist/engineering-pa-cli/src/staging-alias/cli.mjs +91 -0
- package/dist/engineering-pa-cli/src/staging-alias/cloudflare.mjs +380 -0
- package/dist/engineering-pa-cli/src/staging-alias/commands/list.mjs +36 -0
- package/dist/engineering-pa-cli/src/staging-alias/commands/set.mjs +188 -0
- package/dist/engineering-pa-cli/src/staging-alias/commands/status.mjs +113 -0
- package/dist/engineering-pa-cli/src/staging-alias/commands/unset.mjs +95 -0
- package/dist/engineering-pa-cli/src/staging-alias/sites.mjs +105 -0
- package/dist/engineering-pa-cli/src/sync/cli.mjs +151 -0
- package/dist/engineering-pa-cli/src/sync/git.mjs +149 -0
- package/dist/engineering-pa-cli/src/sync/lock.mjs +30 -0
- package/dist/engineering-pa-cli/src/sync/regenerated-artifacts.mjs +114 -0
- package/dist/engineering-pa-cli/src/sync/registry-scope.mjs +260 -0
- package/dist/engineering-pa-cli/src/sync/run.mjs +531 -0
- package/dist/engineering-pa-cli/src/sync/runner.mjs +62 -0
- package/dist/engineering-pa-cli/src/sync/runs-digest.mjs +138 -0
- package/dist/engineering-pa-core/README.md +60 -0
- package/dist/engineering-pa-core/bin/desk +9 -0
- package/dist/engineering-pa-core/bin/distill +2 -0
- package/dist/engineering-pa-core/bin/memory-sync +2 -0
- package/dist/engineering-pa-core/bin/slice +2 -0
- package/dist/engineering-pa-core/docs/autonomy-envelope-tracker.md +111 -0
- package/dist/engineering-pa-core/docs/blueprints/README.md +45 -0
- package/dist/engineering-pa-core/docs/blueprints/docker-services/blueprint.md +168 -0
- package/dist/engineering-pa-core/docs/blueprints/git-identity/blueprint.md +116 -0
- package/dist/engineering-pa-core/docs/blueprints/ideas/custom-workspace-views.md +9 -0
- package/dist/engineering-pa-core/docs/blueprints/ideas/knowledge-domain-patterns.md +15 -0
- package/dist/engineering-pa-core/docs/blueprints/ideas/office-integration.md +11 -0
- package/dist/engineering-pa-core/docs/blueprints/ideas/team-scaling.md +9 -0
- package/dist/engineering-pa-core/docs/blueprints/ideas/work-personal-separation.md +9 -0
- package/dist/engineering-pa-core/docs/blueprints/scheduled-distillation/blueprint.md +129 -0
- package/dist/engineering-pa-core/docs/blueprints/tool-integration/blueprint.md +110 -0
- package/dist/engineering-pa-core/docs/commands/init.md +159 -0
- package/dist/engineering-pa-core/docs/commands/schedule.md +98 -0
- package/dist/engineering-pa-core/docs/commands/upgrade.md +67 -0
- package/dist/engineering-pa-core/docs/entity-location-map.md +93 -0
- package/dist/engineering-pa-core/docs/knowledge-scaffold.md +121 -0
- package/dist/engineering-pa-core/docs/lock.md +155 -0
- package/dist/engineering-pa-core/docs/memory-sync.md +28 -0
- package/dist/engineering-pa-core/docs/pa-operating-model.md +457 -0
- package/dist/engineering-pa-core/docs/scheduling.md +35 -0
- package/dist/engineering-pa-core/docs/standards/README.md +24 -0
- package/dist/engineering-pa-core/docs/standards/briefing-contract.md +161 -0
- package/dist/engineering-pa-core/docs/standards/tooling.md +240 -0
- package/dist/engineering-pa-core/docs/tools/distill.md +230 -0
- package/dist/engineering-pa-core/docs/tools/memory-sync.md +98 -0
- package/dist/engineering-pa-core/docs/tools/scheduler.md +243 -0
- package/dist/engineering-pa-core/docs/tools/slice.md +150 -0
- package/dist/engineering-pa-core/docs/upstream-issue-filing.md +88 -0
- package/dist/engineering-pa-core/docs/working-directory.md +29 -0
- package/dist/engineering-pa-core/docs/workspace-operations.md +46 -0
- package/dist/engineering-pa-core/index.mjs +21 -0
- package/dist/engineering-pa-core/knowledge/README.md +53 -0
- package/dist/engineering-pa-core/knowledge/canonical/.gitkeep +0 -0
- package/dist/engineering-pa-core/knowledge/content-voice/README.md +64 -0
- package/dist/engineering-pa-core/knowledge/content-voice/banned-ai-tells-baseline.md +133 -0
- package/dist/engineering-pa-core/knowledge/content-voice/banned-ai-tells-shape.md +58 -0
- package/dist/engineering-pa-core/knowledge/content-voice/banned-ai-tells-taxonomy.md +33 -0
- package/dist/engineering-pa-core/knowledge/content-voice/content-pipeline-routing.md +58 -0
- package/dist/engineering-pa-core/knowledge/content-voice/new-tell-capture-procedure.md +40 -0
- package/dist/engineering-pa-core/knowledge/content-voice/voice-guide-shape.md +43 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/README.md +34 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/briefing-contract.md +77 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/delegation-modes.md +84 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/dispatch-envelope.md +238 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/task-groups-and-orchestrators.md +62 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/team-improvement-loop.md +48 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/tool-development-policy.md +48 -0
- package/dist/engineering-pa-core/knowledge/engineering-discipline/README.md +33 -0
- package/dist/engineering-pa-core/knowledge/engineering-discipline/blast-radius-and-safety.md +50 -0
- package/dist/engineering-pa-core/knowledge/engineering-discipline/investigate-root-causes.md +48 -0
- package/dist/engineering-pa-core/knowledge/engineering-discipline/role-ladder.md +57 -0
- package/dist/engineering-pa-core/knowledge/engineering-discipline/verify-runtime-behaviour.md +53 -0
- package/dist/engineering-pa-core/knowledge/engineering-discipline/worktree-isolation.md +50 -0
- package/dist/engineering-pa-core/knowledge/knowledge-memory/README.md +33 -0
- package/dist/engineering-pa-core/knowledge/knowledge-memory/claude-md-hygiene.md +47 -0
- package/dist/engineering-pa-core/knowledge/knowledge-memory/journal-and-self-improvement.md +53 -0
- package/dist/engineering-pa-core/knowledge/knowledge-memory/knowledge-retrieval-discipline.md +62 -0
- package/dist/engineering-pa-core/knowledge/knowledge-memory/librarian-synthesis-mandate.md +56 -0
- package/dist/engineering-pa-core/knowledge/knowledge-memory/staging-and-observations.md +58 -0
- package/dist/engineering-pa-core/knowledge/operator-interface/README.md +32 -0
- package/dist/engineering-pa-core/knowledge/operator-interface/communication-style.md +33 -0
- package/dist/engineering-pa-core/knowledge/operator-interface/escalation-and-correction.md +45 -0
- package/dist/engineering-pa-core/knowledge/operator-interface/push-back-discipline.md +42 -0
- package/dist/engineering-pa-core/knowledge/operator-interface/session-boot-and-end.md +56 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/README.md +37 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/desk-cli-reference.md +52 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/environment-awareness.md +91 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/mcp-servers-catalogue.md +52 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/pa-cli-reference.md +55 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/scheduling-and-launchd.md +56 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/tool-discovery-discipline.md +49 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/working-directory-vs-project-root.md +43 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/workspace-server.md +51 -0
- package/dist/engineering-pa-core/knowledge/raw/.gitkeep +0 -0
- package/dist/engineering-pa-core/knowledge/scaffold/domains.json +5 -0
- package/dist/engineering-pa-core/knowledge/schema/.gitkeep +0 -0
- package/dist/engineering-pa-core/knowledge/work-management/README.md +33 -0
- package/dist/engineering-pa-core/knowledge/work-management/desk-cli.md +84 -0
- package/dist/engineering-pa-core/knowledge/work-management/progress-md.md +48 -0
- package/dist/engineering-pa-core/knowledge/work-management/projects.md +64 -0
- package/dist/engineering-pa-core/knowledge/work-management/spawn-log.md +48 -0
- package/dist/engineering-pa-core/knowledge/work-management/work-items.md +67 -0
- package/dist/engineering-pa-core/package.json +26 -0
- package/dist/engineering-pa-core/roles/agent-ops/definition.md +79 -0
- package/dist/engineering-pa-core/roles/dispatch-briefer/definition.md +195 -0
- package/dist/engineering-pa-core/roles/librarian/definition.md +176 -0
- package/dist/engineering-pa-core/roles/pa/definition.md +93 -0
- package/dist/engineering-pa-core/roles/system-admin/definition.md +106 -0
- package/dist/engineering-pa-core/roles/tool-developer/definition.md +116 -0
- package/dist/engineering-pa-core/roles/writer/definition.md +130 -0
- package/dist/engineering-pa-core/schema/autonomy-envelope.schema.json +61 -0
- package/dist/engineering-pa-core/schema/project.json +114 -0
- package/dist/engineering-pa-core/schema/schedule.json +63 -0
- package/dist/engineering-pa-core/schema/work-item.json +92 -0
- package/dist/engineering-pa-core/src/config/pa-package-name.mjs +30 -0
- package/dist/engineering-pa-core/src/desk/cli.mjs +95 -0
- package/dist/engineering-pa-core/src/desk/commands.mjs +398 -0
- package/dist/engineering-pa-core/src/desk/format.mjs +84 -0
- package/dist/engineering-pa-core/src/desk/repo.mjs +172 -0
- package/dist/engineering-pa-core/src/desk/team-scope.mjs +142 -0
- package/dist/engineering-pa-core/src/distill/cli.mjs +112 -0
- package/dist/engineering-pa-core/src/distill/prereqs.mjs +171 -0
- package/dist/engineering-pa-core/src/distill/prompt.mjs +74 -0
- package/dist/engineering-pa-core/src/distill/run.mjs +230 -0
- package/dist/engineering-pa-core/src/distill/runner.mjs +86 -0
- package/dist/engineering-pa-core/src/distill/staging.mjs +59 -0
- package/dist/engineering-pa-core/src/knowledge/retrieval/backlink-indexer.mjs +154 -0
- package/dist/engineering-pa-core/src/knowledge/retrieval/graph-builder.mjs +186 -0
- package/dist/engineering-pa-core/src/knowledge/retrieval/index-builder.mjs +225 -0
- package/dist/engineering-pa-core/src/knowledge/retrieval/query.mjs +263 -0
- package/dist/engineering-pa-core/src/lock/lock.mjs +202 -0
- package/dist/engineering-pa-core/src/memory-sync/cli-support.mjs +12 -0
- package/dist/engineering-pa-core/src/memory-sync/cli.mjs +56 -0
- package/dist/engineering-pa-core/src/memory-sync/commands/pull.mjs +44 -0
- package/dist/engineering-pa-core/src/memory-sync/commands/push.mjs +46 -0
- package/dist/engineering-pa-core/src/memory-sync/mirror.mjs +114 -0
- package/dist/engineering-pa-core/src/memory-sync/repo.mjs +65 -0
- package/dist/engineering-pa-core/src/scheduler/detect.mjs +111 -0
- package/dist/engineering-pa-core/src/scheduler/host.mjs +38 -0
- package/dist/engineering-pa-core/src/scheduler/index.mjs +13 -0
- package/dist/engineering-pa-core/src/scheduler/install.mjs +854 -0
- package/dist/engineering-pa-core/src/scheduler/list.mjs +168 -0
- package/dist/engineering-pa-core/src/scheduler/remove.mjs +176 -0
- package/dist/engineering-pa-core/src/scheduler/verify.mjs +435 -0
- package/dist/engineering-pa-core/src/session-boot/autonomy-injection.mjs +84 -0
- package/dist/engineering-pa-core/src/slice/cli.mjs +139 -0
- package/dist/engineering-pa-core/src/slice/inputs.mjs +174 -0
- package/dist/engineering-pa-core/src/slice/prompt.mjs +94 -0
- package/dist/engineering-pa-core/src/slice/prompts/dave-boot.md +91 -0
- package/dist/engineering-pa-core/src/slice/run.mjs +334 -0
- package/dist/engineering-pa-core-extensions-atlassian/README.md +31 -0
- package/dist/engineering-pa-core-extensions-atlassian/index.mjs +9 -0
- package/dist/engineering-pa-core-extensions-atlassian/package.json +19 -0
- package/dist/engineering-pa-core-extensions-n8n/README.md +29 -0
- package/dist/engineering-pa-core-extensions-n8n/index.mjs +8 -0
- package/dist/engineering-pa-core-extensions-n8n/package.json +19 -0
- package/dist/engineering-pa-librarian/README.md +38 -0
- package/dist/engineering-pa-librarian/index.mjs +12 -0
- package/dist/engineering-pa-librarian/package.json +19 -0
- package/dist/engineering-pa-migrate-inspect/README.md +131 -0
- package/dist/engineering-pa-migrate-inspect/bin/migrate-inspect +2 -0
- package/dist/engineering-pa-migrate-inspect/package.json +21 -0
- package/dist/engineering-pa-migrate-inspect/src/age-key-migrate.mjs +83 -0
- package/dist/engineering-pa-migrate-inspect/src/cli.mjs +97 -0
- package/dist/engineering-pa-migrate-inspect/src/inspector.mjs +237 -0
- package/dist/engineering-pa-migrate-inspect/src/platforms/linux.mjs +87 -0
- package/dist/engineering-pa-migrate-inspect/src/platforms/macos.mjs +105 -0
- package/dist/engineering-pa-migrate-inspect/src/runner.mjs +87 -0
- package/dist/engineering-pa-runbook/README.md +103 -0
- package/dist/engineering-pa-runbook/package.json +18 -0
- package/dist/engineering-pa-runbook/reference/dex-template.runbook.json +66 -0
- package/dist/engineering-pa-runbook/reference/hq-dave.runbook.json +69 -0
- package/dist/engineering-pa-runbook/reference/smarthome01.runbook.json +92 -0
- package/dist/engineering-pa-runbook/schema/inspection.schema.json +113 -0
- package/dist/engineering-pa-runbook/schema/runbook.schema.json +67 -0
- package/dist/engineering-pa-runbook/src/validator.mjs +167 -0
- package/dist/engineering-pa-scaffold/README.md +164 -0
- package/dist/engineering-pa-scaffold/bin/pa-init +2 -0
- package/dist/engineering-pa-scaffold/index.mjs +42 -0
- package/dist/engineering-pa-scaffold/package.json +22 -0
- package/dist/engineering-pa-scaffold/src/cli.mjs +115 -0
- package/dist/engineering-pa-scaffold/src/commands/init.mjs +169 -0
- package/dist/engineering-pa-scaffold/src/generator.mjs +354 -0
- package/dist/engineering-pa-scaffold/src/prompts.mjs +125 -0
- package/dist/engineering-pa-scaffold/src/runner.mjs +142 -0
- package/dist/engineering-pa-scaffold/templates/CLAUDE.md.template +196 -0
- package/dist/engineering-pa-scaffold/templates/operator-README.md.template +44 -0
- package/dist/engineering-pa-types-engineering/README.md +35 -0
- package/dist/engineering-pa-types-engineering/docs/commands/product-build.md +120 -0
- package/dist/engineering-pa-types-engineering/index.mjs +9 -0
- package/dist/engineering-pa-types-engineering/package.json +19 -0
- package/dist/engineering-pa-types-engineering/roles/product-engineer/definition.md +650 -0
- package/dist/engineering-pa-types-engineering/roles/product-engineer/templates/build-spec.md +82 -0
- package/dist/engineering-pa-types-engineering/roles/product-engineer/templates/preflight-checklist.md +158 -0
- package/dist/engineering-pa-types-engineering/roles/product-engineer/templates/worker-brief.md +130 -0
- package/dist/engineering-pa-workspace/README.md +197 -0
- package/dist/engineering-pa-workspace/app/assets/md.js +187 -0
- package/dist/engineering-pa-workspace/app/assets/modal.js +120 -0
- package/dist/engineering-pa-workspace/app/assets/theme.css +132 -0
- package/dist/engineering-pa-workspace/app/assets/theme.js +159 -0
- package/dist/engineering-pa-workspace/app/banner.js +320 -0
- package/dist/engineering-pa-workspace/app/delegations.html +345 -0
- package/dist/engineering-pa-workspace/app/home.html +1060 -0
- package/dist/engineering-pa-workspace/app/index.html +361 -0
- package/dist/engineering-pa-workspace/app/knowledge-map.html +163 -0
- package/dist/engineering-pa-workspace/app/knowledge.html +304 -0
- package/dist/engineering-pa-workspace/app/nav.json +75 -0
- package/dist/engineering-pa-workspace/app/notes.html +199 -0
- package/dist/engineering-pa-workspace/app/onboarding.html +592 -0
- package/dist/engineering-pa-workspace/app/profile.html +243 -0
- package/dist/engineering-pa-workspace/app/project-detail.html +432 -0
- package/dist/engineering-pa-workspace/app/projects.html +297 -0
- package/dist/engineering-pa-workspace/app/repos.html +598 -0
- package/dist/engineering-pa-workspace/app/schedules.html +486 -0
- package/dist/engineering-pa-workspace/app/secrets.html +164 -0
- package/dist/engineering-pa-workspace/app/staging.html +319 -0
- package/dist/engineering-pa-workspace/app/tasks.html +465 -0
- package/dist/engineering-pa-workspace/app/vendor/chart.umd.min.js +21 -0
- package/dist/engineering-pa-workspace/app/viewer.html +116 -0
- package/dist/engineering-pa-workspace/docs/DAEMON.md +104 -0
- package/dist/engineering-pa-workspace/docs/page-blueprint.md +286 -0
- package/dist/engineering-pa-workspace/docs/status-mon.md +168 -0
- package/dist/engineering-pa-workspace/index.mjs +12 -0
- package/dist/engineering-pa-workspace/nav.json.example +8 -0
- package/dist/engineering-pa-workspace/package.json +27 -0
- package/dist/engineering-pa-workspace/src/api.mjs +2086 -0
- package/dist/engineering-pa-workspace/src/install-daemon.mjs +573 -0
- package/dist/engineering-pa-workspace/src/nav.mjs +190 -0
- package/dist/engineering-pa-workspace/src/secrets-api.mjs +175 -0
- package/dist/engineering-pa-workspace/src/server.mjs +516 -0
- package/dist/engineering-pa-workspace/src/status-mon/poll.mjs +220 -0
- package/dist/engineering-pa-workspace/src/status-mon/poller.mjs +113 -0
- package/dist/engineering-pa-workspace/src/status-mon/run.mjs +133 -0
- package/dist/engineering-pa-workspace/src/supervisor.mjs +264 -0
- package/dist/engineering-pa-workspace/src/ws.mjs +633 -0
- package/index.mjs +60 -0
- package/package.json +58 -0
- package/templates/banned-ai-tells.md +102 -0
- package/templates/purpose.md +105 -0
- package/templates/voice-guide.md +98 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# pa launch — agent launcher
|
|
2
|
+
|
|
3
|
+
Identity: `@stravica-ai/engineering-pa-cli` › `launch` — boot a scoped agent instance bound to an identity and task.
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
**Shipped.** Handles single-agent single-task execution against any operator repo with roster and schedule files. Dry-run mode. Audit log per tooling standard §15. Per-task concurrency lock, default-on (see "Concurrency lock" below).
|
|
8
|
+
|
|
9
|
+
## Install / invoke
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Via pa top-level CLI
|
|
13
|
+
pnpm exec pa launch --agent=<name> --task=<id> [--dry-run]
|
|
14
|
+
|
|
15
|
+
# Via standalone bin
|
|
16
|
+
pnpm exec launch --agent=<name> --task=<id> [--dry-run]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Commands
|
|
20
|
+
|
|
21
|
+
### `launch --agent=<name> --task=<id> [--dry-run]`
|
|
22
|
+
|
|
23
|
+
Boots a scoped agent instance:
|
|
24
|
+
|
|
25
|
+
1. Resolves the operator repo root (`DAVE_REPO_ROOT` / `PA_REPO_ROOT` env var, or `.git` walk from `cwd`).
|
|
26
|
+
2. Loads identity from `operator/knowledge/hr/roster/<name>.json`.
|
|
27
|
+
3. Loads task definition from `schedules/<id>.json`.
|
|
28
|
+
4. Builds a `LaunchPlan` (pure — no I/O).
|
|
29
|
+
5. On `--dry-run`: prints the plan as JSON, exits 0.
|
|
30
|
+
6. Otherwise: via the chokepoint runner — creates intake folder, spawns task command, writes audit record.
|
|
31
|
+
|
|
32
|
+
**Flags:**
|
|
33
|
+
|
|
34
|
+
| Flag | Required | Description |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `--agent` | yes | Agent name — maps to `operator/knowledge/hr/roster/<name>.json` |
|
|
37
|
+
| `--task` | yes | Task ID — maps to `schedules/<id>.json` |
|
|
38
|
+
| `--dry-run` | no | Print LaunchPlan as JSON; skip execution and audit write |
|
|
39
|
+
| `-h`, `--help` | no | Show usage |
|
|
40
|
+
|
|
41
|
+
## Identity file schema
|
|
42
|
+
|
|
43
|
+
Loaded from `operator/knowledge/hr/roster/<name>.json`. **Format: JSON.** No YAML/frontmatter parser — the roster is already JSON across the platform.
|
|
44
|
+
|
|
45
|
+
Required fields:
|
|
46
|
+
|
|
47
|
+
| Field | Type | Description |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `id` | string | Agent identifier (must match filename stem) |
|
|
50
|
+
| `name` | string | Display name |
|
|
51
|
+
| `roleId` | string | Role identifier (e.g. `system-admin`) |
|
|
52
|
+
| `runFolder` | string | Path relative to repo root (e.g. `agents/ian`) |
|
|
53
|
+
| `status` | string | Agent status (`active` \| other) |
|
|
54
|
+
|
|
55
|
+
Optional fields (`currentTeam`, `createdBy`, `createdDate`, `history`) are preserved but not read by the launcher.
|
|
56
|
+
|
|
57
|
+
## Task definition schema
|
|
58
|
+
|
|
59
|
+
Loaded from `schedules/<id>.json`. **Format: JSON.**
|
|
60
|
+
|
|
61
|
+
Required fields:
|
|
62
|
+
|
|
63
|
+
| Field | Type | Description |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `id` | string | Task identifier (must match filename stem) |
|
|
66
|
+
| `command` | string | Command to execute — split on whitespace, passed to `spawn` without a shell. **Must not contain `'` or `"`** (no shell-quoting — see below) |
|
|
67
|
+
|
|
68
|
+
Optional fields:
|
|
69
|
+
|
|
70
|
+
| Field | Type | Description |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `workingDirectory` | string | cwd for spawn; supports `${DAVE_ROOT}`, `${PA_REPO_ROOT}`, `${DAVE_REPO_ROOT}` tokens |
|
|
73
|
+
| `name` | string | Display name |
|
|
74
|
+
| `description` | string | Human-readable description |
|
|
75
|
+
| `schedule` | string | Cron expression (read by cron — not used by launcher) |
|
|
76
|
+
| `enabled` | boolean | Read by scheduler — not used by launcher |
|
|
77
|
+
| `lockStaleAfterMinutes` | number | Per-task staleness override for the concurrency lock (see below). Default: the core lock's 2-hour threshold |
|
|
78
|
+
|
|
79
|
+
## `command` is not a shell expression
|
|
80
|
+
|
|
81
|
+
The runner spawns the command **directly** — no shell, no expansion, no operators. The string is split on whitespace and handed to `child_process.spawn()` as `(bin, args[])`.
|
|
82
|
+
|
|
83
|
+
If the command contains `'` or `"`, `task-loader` rejects it at load time with exit code 2 (a `--dry-run` also fails). This is a deliberate guard: silently splitting `bash -c "a && b"` on whitespace shreds it to `spawn('bash', ['-c', '"a', '&&', 'b"'])`, bash exits 2 with `unexpected EOF`, and cron mails the operator every interval until someone notices.
|
|
84
|
+
|
|
85
|
+
If you need shell features, you have two options:
|
|
86
|
+
|
|
87
|
+
1. **Wrap the work in a script file** and put the script path in `command`: `command: "/path/to/wrapper.sh"`. The runner spawns the script; the script does whatever it likes.
|
|
88
|
+
2. **Move the shell wrapper to the cron entry**, not the task command. The scheduler installer can take a quoted bash invocation in the cron crontab; the launcher's `command` field cannot.
|
|
89
|
+
|
|
90
|
+
## Concurrency lock (default-on)
|
|
91
|
+
|
|
92
|
+
Two runs of the **same task** must not overlap — a second concurrent run
|
|
93
|
+
consumes the same inputs (staging areas) and produces duplicate effects and
|
|
94
|
+
git races. Every non-dry-run launch therefore acquires a per-task lock via
|
|
95
|
+
the shared core utility (`@stravica-ai/engineering-pa-core` › `acquireLock` —
|
|
96
|
+
full contract in `packages/core/docs/lock.md`) before spawning. No opt-in
|
|
97
|
+
flag; protection is the default.
|
|
98
|
+
|
|
99
|
+
- **Lock path:** `<repoRoot>/.cache/launch/<taskId>.lock` — per task, so two
|
|
100
|
+
*different* tasks overlap freely. `.cache/` is the gitignored
|
|
101
|
+
operational-state area (tooling standard §16).
|
|
102
|
+
- **On contention** (live same-task lock): the run **exits 0** with a
|
|
103
|
+
greppable skip line on stdout —
|
|
104
|
+
`[launch] <agent>/<task>: skipped — lock held (held by pid <pid> since <ts>; lock <path>)`
|
|
105
|
+
— writes a `noop-locked` audit record, and spawns nothing. Exit 0 (not 2)
|
|
106
|
+
because an overlap skip is a healthy no-op on a cron schedule, matching
|
|
107
|
+
`sync`'s `noop-locked` precedent; exit 2 would generate cron mail for
|
|
108
|
+
expected behaviour. Automation that needs to detect skips reads the audit
|
|
109
|
+
record or greps the log line.
|
|
110
|
+
- **Stale locks are broken automatically** with a
|
|
111
|
+
`[launch] warning: broke stale lock ...` line on stderr, and the run
|
|
112
|
+
proceeds. Staleness: dead pid (primary — recovers crashed runs
|
|
113
|
+
immediately), or age past the threshold (backstop for pid reuse /
|
|
114
|
+
unreadable lock files). Threshold: the core default of **2 hours**
|
|
115
|
+
(sized so legitimately long sweeps — observed 1288+ seconds — are never
|
|
116
|
+
stolen mid-run), overridable per task via `lockStaleAfterMinutes` in
|
|
117
|
+
`schedules/<id>.json`.
|
|
118
|
+
- **Release:** on every exit path (success, task failure, spawn error). A
|
|
119
|
+
crashed run leaves a dead-pid lock that the next tick breaks.
|
|
120
|
+
- **First run:** no lock file or `.cache/launch/` directory needed; the
|
|
121
|
+
empty state is the common case.
|
|
122
|
+
|
|
123
|
+
## LaunchPlan object
|
|
124
|
+
|
|
125
|
+
The planner produces a `LaunchPlan` (JSON-serialisable, no I/O):
|
|
126
|
+
|
|
127
|
+
```jsonc
|
|
128
|
+
{
|
|
129
|
+
"repoRoot": "/abs/path/to/operator-repo",
|
|
130
|
+
"agent": { "id": "ian", "name": "Ian", "roleId": "system-admin", "runFolder": "agents/ian" },
|
|
131
|
+
"task": { "id": "upstream-triage", "command": "node ...", "workingDirectory": null, "description": null },
|
|
132
|
+
"intakeFolderPath": "/abs/path/to/agents/ian/staging",
|
|
133
|
+
"auditLogPath": "/abs/path/to/agents/ian/runs.jsonl",
|
|
134
|
+
"lockPath": "/abs/path/to/.cache/launch/upstream-triage.lock",
|
|
135
|
+
"lockStaleAfterMs": null // null = core default (2 h); set via task lockStaleAfterMinutes
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Chokepoint discipline
|
|
140
|
+
|
|
141
|
+
`runner.mjs` is the **sole module** that:
|
|
142
|
+
- Creates directories (`mkdir`)
|
|
143
|
+
- Appends audit records (`appendFile`)
|
|
144
|
+
- Acquires/releases the per-task concurrency lock
|
|
145
|
+
- Spawns child processes (`spawn`)
|
|
146
|
+
|
|
147
|
+
The lock lives in the chokepoint so programmatic callers cannot bypass it:
|
|
148
|
+
a plan handed to `execute()` without a `lockPath` still gets a derived
|
|
149
|
+
per-task lock.
|
|
150
|
+
|
|
151
|
+
All other modules (`identity.mjs`, `task-loader.mjs`, `planner.mjs`) are pure — no I/O, no spawning. Tests inject a fake spawner into the runner; no mock libraries.
|
|
152
|
+
|
|
153
|
+
Launched agents **must not** do git operations. They write files to disk; the sync tool (`pa sync`) picks up changes on its tick.
|
|
154
|
+
|
|
155
|
+
## Exit codes (tooling standard §6)
|
|
156
|
+
|
|
157
|
+
| Code | Meaning |
|
|
158
|
+
|---|---|
|
|
159
|
+
| `0` | OK |
|
|
160
|
+
| `1` | Usage error (missing flags, unknown flag) |
|
|
161
|
+
| `2` | Logical failure (agent not found, task not found, missing required field) |
|
|
162
|
+
| `3` | Transport / IO failure (file unreadable, spawn failed) |
|
|
163
|
+
|
|
164
|
+
## Audit record contract (tooling standard §15)
|
|
165
|
+
|
|
166
|
+
Every non-dry-run exit path appends one JSON line to `<runFolder>/runs.jsonl`:
|
|
167
|
+
|
|
168
|
+
```jsonc
|
|
169
|
+
{ "ts": "2026-05-17T12:00:00Z", "status": "ok", "agentId": "ian", "taskId": "upstream-triage", "exitCode": 0 }
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`status` vocabulary: `ok` | `noop-locked` | `fail-exit-<N>` | `fail-spawn`
|
|
173
|
+
|
|
174
|
+
`noop-locked` records carry a `reason` field naming the lock holder
|
|
175
|
+
(`"lock contention: held by pid <pid> since <ts>"`). Per §15 monitors treat
|
|
176
|
+
`noop-*` as healthy/fresh.
|
|
177
|
+
|
|
178
|
+
`runs.jsonl` is an operational artefact — it must be gitignored in the operator repo. It is never committed.
|
|
179
|
+
|
|
180
|
+
## Project layout
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
packages/cli/src/launch/
|
|
184
|
+
README.md this file — the contract
|
|
185
|
+
cli.mjs arg parsing, flag validation, exit-code translation
|
|
186
|
+
commands/
|
|
187
|
+
launch.mjs thin glue: load → plan → execute
|
|
188
|
+
identity.mjs pure: load + validate agent identity from roster JSON
|
|
189
|
+
task-loader.mjs pure: load + validate task definition from schedules JSON
|
|
190
|
+
planner.mjs pure: build LaunchPlan object (no I/O)
|
|
191
|
+
repo-root.mjs pure: operator repo root discovery (env var + .git walk)
|
|
192
|
+
runner.mjs CHOKEPOINT: all file writes + process spawn
|
|
193
|
+
packages/cli/test/launch/
|
|
194
|
+
cli.test.mjs end-to-end: --help, --dry-run, live execution, error paths
|
|
195
|
+
identity.test.mjs unit: happy path, missing agent, invalid JSON, missing field
|
|
196
|
+
task-loader.test.mjs unit: happy path, missing task, invalid JSON, missing field
|
|
197
|
+
planner.test.mjs unit: output shape, null defaults, purity
|
|
198
|
+
runner.test.mjs unit: ok/fail/spawn-error paths via fake spawner
|
|
199
|
+
fixtures/ synthetic roster + schedule for tests
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Tests
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# Run launch tests only
|
|
206
|
+
node --test 'packages/cli/test/launch/**/*.test.mjs'
|
|
207
|
+
|
|
208
|
+
# Run all CLI package tests
|
|
209
|
+
pnpm --filter @stravica-ai/engineering-pa-cli test
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Standards
|
|
213
|
+
|
|
214
|
+
Follows the [tooling standard](../../../../core/knowledge/standards/tooling.md): Node.js only, zero npm deps, `node:test`, `node:util.parseArgs` with `strict: true`, camelCase JSON, chokepoint pattern (§7), audit record contract (§15), gitignore operational artefacts (§16).
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// cli.mjs — pa launch: boot a scoped agent instance bound to an identity and task.
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
|
|
4
|
+
const USAGE = `pa launch — boot a scoped agent instance bound to an identity and task
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
pa launch --agent=<name> --task=<id> [--dry-run]
|
|
8
|
+
pa launch -h, --help
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
--agent Agent name — maps to operator/knowledge/hr/roster/<name>.json
|
|
12
|
+
--task Task ID — maps to schedules/<id>.json
|
|
13
|
+
--dry-run Print the LaunchPlan as JSON without executing
|
|
14
|
+
-h, --help Show this help
|
|
15
|
+
|
|
16
|
+
Exit codes (tooling standard §6):
|
|
17
|
+
0 ok | 1 usage error | 2 operational failure | 3 transport/IO failure
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
export async function run(argv) {
|
|
21
|
+
if (argv.includes('-h') || argv.includes('--help')) {
|
|
22
|
+
process.stdout.write(USAGE);
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let values;
|
|
27
|
+
try {
|
|
28
|
+
({ values } = parseArgs({
|
|
29
|
+
args: argv,
|
|
30
|
+
options: {
|
|
31
|
+
agent: { type: 'string' },
|
|
32
|
+
task: { type: 'string' },
|
|
33
|
+
'dry-run': { type: 'boolean', default: false },
|
|
34
|
+
},
|
|
35
|
+
strict: true,
|
|
36
|
+
}));
|
|
37
|
+
} catch (err) {
|
|
38
|
+
process.stderr.write(`[pa launch] ${err.message}\n`);
|
|
39
|
+
process.stderr.write(USAGE);
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!values.agent || !values.task) {
|
|
44
|
+
process.stderr.write('[pa launch] --agent and --task are required\n');
|
|
45
|
+
process.stderr.write(USAGE);
|
|
46
|
+
return 1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const { executeLaunch } = await import('./commands/launch.mjs');
|
|
50
|
+
return await executeLaunch(values.agent, values.task, { dryRun: values['dry-run'] });
|
|
51
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// launch.mjs — thin glue: load identity → load task → build plan → execute.
|
|
2
|
+
import { hostname } from 'node:os';
|
|
3
|
+
|
|
4
|
+
import { hostMatches } from '../../../../engineering-pa-core/src/scheduler/host.mjs';
|
|
5
|
+
|
|
6
|
+
import { getRepoRoot } from '../repo-root.mjs';
|
|
7
|
+
import { loadIdentity } from '../identity.mjs';
|
|
8
|
+
import { loadTask } from '../task-loader.mjs';
|
|
9
|
+
import { buildPlan } from '../planner.mjs';
|
|
10
|
+
import { execute } from '../runner.mjs';
|
|
11
|
+
|
|
12
|
+
// Override env var for the host-enforcement gate below — mirrors
|
|
13
|
+
// `scheduler install`'s `--force-host` CLI flag, but as an env var since
|
|
14
|
+
// `launch` is invoked automatically by cron/launchd/systemd (no operator
|
|
15
|
+
// present to pass a flag). Same override SEMANTICS as install, different
|
|
16
|
+
// delivery mechanism because the calling context is different.
|
|
17
|
+
const FORCE_HOST_ENV = 'DAVE_FORCE_HOST';
|
|
18
|
+
|
|
19
|
+
export async function executeLaunch(agentName, taskId, { dryRun = false } = {}) {
|
|
20
|
+
let repoRoot;
|
|
21
|
+
try {
|
|
22
|
+
repoRoot = await getRepoRoot();
|
|
23
|
+
} catch (err) {
|
|
24
|
+
process.stderr.write(`[launch] ${err.message}\n`);
|
|
25
|
+
return err.exitCode ?? 2;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let identity;
|
|
29
|
+
try {
|
|
30
|
+
identity = await loadIdentity(repoRoot, agentName);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
process.stderr.write(`[launch] identity: ${err.message}\n`);
|
|
33
|
+
return err.exitCode ?? 2;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let task;
|
|
37
|
+
try {
|
|
38
|
+
task = await loadTask(repoRoot, taskId);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
process.stderr.write(`[launch] task: ${err.message}\n`);
|
|
41
|
+
return err.exitCode ?? 2;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Host-enforcement gate — refuse to RUN a task whose schedule file
|
|
45
|
+
// declares a foreign host, mirroring `scheduler install`'s refusal.
|
|
46
|
+
// Missing `host` field is not a constraint (data backfill is
|
|
47
|
+
// estate-side); DAVE_FORCE_HOST=1 overrides a mismatch, same semantics
|
|
48
|
+
// as install's --force-host, just delivered via env since launch is
|
|
49
|
+
// invoked non-interactively. --dry-run is judgement-call exempt from
|
|
50
|
+
// the hard refusal (it never executes anything — a plan preview from a
|
|
51
|
+
// non-owning host, e.g. a Project Lead debugging another host's task,
|
|
52
|
+
// is legitimate) but still surfaces the mismatch as a warning so it's
|
|
53
|
+
// never silently invisible.
|
|
54
|
+
if (task.host) {
|
|
55
|
+
const currentHost = hostname();
|
|
56
|
+
const forceHost = process.env[FORCE_HOST_ENV] === '1' || process.env[FORCE_HOST_ENV] === 'true';
|
|
57
|
+
if (!hostMatches(task.host, currentHost)) {
|
|
58
|
+
if (dryRun) {
|
|
59
|
+
process.stderr.write(
|
|
60
|
+
`[launch] warning: task "${taskId}" declares host "${task.host}" but this machine is `
|
|
61
|
+
+ `"${currentHost}" — dry-run proceeds (would refuse on a real run without ${FORCE_HOST_ENV}=1)\n`,
|
|
62
|
+
);
|
|
63
|
+
} else if (forceHost) {
|
|
64
|
+
process.stderr.write(
|
|
65
|
+
`[launch] warning: task "${taskId}" declares host "${task.host}" but this machine is `
|
|
66
|
+
+ `"${currentHost}" — proceeding anyway due to ${FORCE_HOST_ENV}=1\n`,
|
|
67
|
+
);
|
|
68
|
+
} else {
|
|
69
|
+
process.stderr.write(
|
|
70
|
+
`[launch] task "${taskId}" declares host "${task.host}" but this machine is `
|
|
71
|
+
+ `"${currentHost}" — refusing to run. Set ${FORCE_HOST_ENV}=1 to override.\n`,
|
|
72
|
+
);
|
|
73
|
+
return 2;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const plan = buildPlan(repoRoot, identity, task);
|
|
79
|
+
|
|
80
|
+
if (dryRun) {
|
|
81
|
+
process.stdout.write(JSON.stringify(plan, null, 2) + '\n');
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const result = await execute(plan);
|
|
87
|
+
if (result.status === 'noop-locked') {
|
|
88
|
+
// Overlapping run of the same task — healthy skip, exit 0 (cron
|
|
89
|
+
// treats it as a no-op; sync's noop-locked precedent). Greppable.
|
|
90
|
+
process.stdout.write(
|
|
91
|
+
`[launch] ${agentName}/${taskId}: skipped — lock held (${result.reason}; lock ${result.lockPath})\n`,
|
|
92
|
+
);
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
if (result.status === 'ok') {
|
|
96
|
+
process.stdout.write(`[launch] ${agentName}/${taskId}: ok (exit ${result.exitCode})\n`);
|
|
97
|
+
return 0;
|
|
98
|
+
} else {
|
|
99
|
+
process.stderr.write(`[launch] ${agentName}/${taskId}: ${result.status} (exit ${result.exitCode})\n`);
|
|
100
|
+
return 2;
|
|
101
|
+
}
|
|
102
|
+
} catch (err) {
|
|
103
|
+
process.stderr.write(`[launch] runner: ${err.message}\n`);
|
|
104
|
+
return err.exitCode ?? 3;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// identity.mjs — pure: load + validate an agent identity from the operator roster.
|
|
2
|
+
// Roster path: <repoRoot>/operator/knowledge/hr/roster/<name>.json
|
|
3
|
+
// Required fields: id, name, roleId, runFolder, status
|
|
4
|
+
import { readFile } from 'node:fs/promises';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
const REQUIRED_FIELDS = ['id', 'name', 'roleId', 'runFolder', 'status'];
|
|
8
|
+
|
|
9
|
+
export async function loadIdentity(repoRoot, agentName) {
|
|
10
|
+
const path = join(repoRoot, 'operator', 'knowledge', 'hr', 'roster', `${agentName}.json`);
|
|
11
|
+
let raw;
|
|
12
|
+
try {
|
|
13
|
+
raw = await readFile(path, 'utf8');
|
|
14
|
+
} catch (err) {
|
|
15
|
+
if (err.code === 'ENOENT') {
|
|
16
|
+
const e = new Error(`agent "${agentName}" not found in roster (${path})`);
|
|
17
|
+
e.exitCode = 2;
|
|
18
|
+
throw e;
|
|
19
|
+
}
|
|
20
|
+
const e = new Error(`could not read roster file for "${agentName}": ${err.message}`);
|
|
21
|
+
e.exitCode = 3;
|
|
22
|
+
throw e;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let data;
|
|
26
|
+
try {
|
|
27
|
+
data = JSON.parse(raw);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
const e = new Error(`roster file for "${agentName}" is not valid JSON: ${err.message}`);
|
|
30
|
+
e.exitCode = 2;
|
|
31
|
+
throw e;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const field of REQUIRED_FIELDS) {
|
|
35
|
+
if (!data[field]) {
|
|
36
|
+
const e = new Error(`identity "${agentName}": missing required field "${field}"`);
|
|
37
|
+
e.exitCode = 2;
|
|
38
|
+
throw e;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return data;
|
|
43
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// planner.mjs — pure: produce a LaunchPlan from loaded identity + task.
|
|
2
|
+
// No I/O. No spawning. Returns a plain object.
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* LaunchPlan shape:
|
|
7
|
+
* repoRoot — absolute path to the operator repo
|
|
8
|
+
* agent — { id, name, roleId, runFolder }
|
|
9
|
+
* task — { id, command, workingDirectory, description }
|
|
10
|
+
* intakeFolderPath — absolute path to agents/<name>/staging/
|
|
11
|
+
* auditLogPath — absolute path to agents/<name>/runs.jsonl
|
|
12
|
+
* lockPath — absolute path to .cache/launch/<taskId>.lock
|
|
13
|
+
* (per-task: two different tasks may overlap freely;
|
|
14
|
+
* two runs of the SAME task must not. `.cache/` is
|
|
15
|
+
* the platform's gitignored operational-state area —
|
|
16
|
+
* tooling standard §16.)
|
|
17
|
+
* lockStaleAfterMs — per-task staleness override in ms, from the task's
|
|
18
|
+
* optional `lockStaleAfterMinutes` field; null means
|
|
19
|
+
* "use the core lock default" (2 h — see
|
|
20
|
+
* packages/core/docs/lock.md for the rationale).
|
|
21
|
+
*/
|
|
22
|
+
export function buildPlan(repoRoot, identity, task) {
|
|
23
|
+
return {
|
|
24
|
+
repoRoot,
|
|
25
|
+
agent: {
|
|
26
|
+
id: identity.id,
|
|
27
|
+
name: identity.name,
|
|
28
|
+
roleId: identity.roleId,
|
|
29
|
+
runFolder: identity.runFolder,
|
|
30
|
+
},
|
|
31
|
+
task: {
|
|
32
|
+
id: task.id,
|
|
33
|
+
command: task.command,
|
|
34
|
+
workingDirectory: task.workingDirectory ?? null,
|
|
35
|
+
description: task.description ?? null,
|
|
36
|
+
},
|
|
37
|
+
intakeFolderPath: join(repoRoot, identity.runFolder, 'staging'),
|
|
38
|
+
auditLogPath: join(repoRoot, identity.runFolder, 'runs.jsonl'),
|
|
39
|
+
lockPath: join(repoRoot, '.cache', 'launch', `${task.id}.lock`),
|
|
40
|
+
lockStaleAfterMs: Number.isFinite(task.lockStaleAfterMinutes) && task.lockStaleAfterMinutes > 0
|
|
41
|
+
? task.lockStaleAfterMinutes * 60 * 1000
|
|
42
|
+
: null,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// repo-root.mjs — operator repo root discovery for the launch CLI.
|
|
2
|
+
// Mirrors the pattern in packages/core/src/hq/repo.mjs; own copy avoids
|
|
3
|
+
// cross-package coupling for a pure utility.
|
|
4
|
+
import { stat } from 'node:fs/promises';
|
|
5
|
+
import { join, resolve, dirname } from 'node:path';
|
|
6
|
+
|
|
7
|
+
async function pathKind(p) {
|
|
8
|
+
try {
|
|
9
|
+
const s = await stat(p);
|
|
10
|
+
if (s.isDirectory()) return 'dir';
|
|
11
|
+
if (s.isFile()) return 'file';
|
|
12
|
+
return 'other';
|
|
13
|
+
} catch {
|
|
14
|
+
return 'missing';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function findRepoRoot(start) {
|
|
19
|
+
let d = start;
|
|
20
|
+
while (d && d !== dirname(d)) {
|
|
21
|
+
const k = await pathKind(join(d, '.git'));
|
|
22
|
+
if (k === 'dir' || k === 'file') return d;
|
|
23
|
+
d = dirname(d);
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let _cache = null;
|
|
29
|
+
|
|
30
|
+
export async function getRepoRoot() {
|
|
31
|
+
if (_cache) return _cache;
|
|
32
|
+
const envRoot = process.env.DAVE_REPO_ROOT || process.env.PA_REPO_ROOT;
|
|
33
|
+
if (envRoot) {
|
|
34
|
+
_cache = resolve(envRoot);
|
|
35
|
+
return _cache;
|
|
36
|
+
}
|
|
37
|
+
const found = await findRepoRoot(process.cwd());
|
|
38
|
+
if (!found) {
|
|
39
|
+
const err = new Error(
|
|
40
|
+
'could not find operator repo root from ' + process.cwd() +
|
|
41
|
+
' (no .git ancestor; set DAVE_REPO_ROOT or PA_REPO_ROOT to override)',
|
|
42
|
+
);
|
|
43
|
+
err.exitCode = 2;
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
_cache = found;
|
|
47
|
+
return _cache;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Test seam: reset the cached root between test cases.
|
|
51
|
+
export function _resetRepoRoot() {
|
|
52
|
+
_cache = null;
|
|
53
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// runner.mjs — CHOKEPOINT: all file writes outside this module + all process spawn.
|
|
2
|
+
//
|
|
3
|
+
// Discipline: pure modules (identity, task-loader, planner) never touch disk.
|
|
4
|
+
// The runner is the sole module that creates directories, appends audit logs,
|
|
5
|
+
// acquires the per-task lock, and spawns child processes. Tests inject a fake
|
|
6
|
+
// spawner to avoid real I/O.
|
|
7
|
+
//
|
|
8
|
+
// Concurrency lock (default-on): before spawning, the runner acquires the
|
|
9
|
+
// per-task lock at plan.lockPath via the shared core utility
|
|
10
|
+
// (packages/core/docs/lock.md). Two runs of the same task must not overlap —
|
|
11
|
+
// the second run returns `noop-locked` (no spawn, audit record written, the
|
|
12
|
+
// caller exits 0 so cron treats the skip as a healthy no-op, matching sync's
|
|
13
|
+
// precedent). Stale locks (dead pid, or older than the staleness threshold —
|
|
14
|
+
// core default 2 h, per-task override via the task's `lockStaleAfterMinutes`)
|
|
15
|
+
// are broken automatically with a logged warning and the run proceeds. The
|
|
16
|
+
// lock is released on every exit path; a crashed run leaves a dead-pid lock
|
|
17
|
+
// that the next tick breaks.
|
|
18
|
+
//
|
|
19
|
+
// Audit record contract (tooling standard §15):
|
|
20
|
+
// status — "ok" | "noop-locked" | "fail-exit-<N>" | "fail-spawn"
|
|
21
|
+
// ts — RFC-3339 (YYYY-MM-DDTHH:MM:SSZ, no milliseconds)
|
|
22
|
+
import { mkdir, appendFile } from 'node:fs/promises';
|
|
23
|
+
import { spawn } from 'node:child_process';
|
|
24
|
+
import { join } from 'node:path';
|
|
25
|
+
// Subpath import (not the bare package) — the engineering-pa dist bundle
|
|
26
|
+
// rewrites cross-package import strings to relative paths, and a bare
|
|
27
|
+
// package import would rewrite to a directory path ESM cannot resolve.
|
|
28
|
+
import { acquireLock } from '../../../engineering-pa-core/src/lock/lock.mjs';
|
|
29
|
+
import { assertNoShellQuoting } from './task-loader.mjs';
|
|
30
|
+
|
|
31
|
+
function resolveWorkdir(workdir, repoRoot) {
|
|
32
|
+
if (!workdir) return repoRoot;
|
|
33
|
+
return workdir
|
|
34
|
+
.replace(/\$\{DAVE_ROOT\}/g, repoRoot)
|
|
35
|
+
.replace(/\$\{PA_REPO_ROOT\}/g, repoRoot)
|
|
36
|
+
.replace(/\$\{DAVE_REPO_ROOT\}/g, repoRoot);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Parse a task command string into { bin, args } suitable for spawn().
|
|
41
|
+
*
|
|
42
|
+
* Contract: task commands are STRUCTURED (bin + literal args), not shell
|
|
43
|
+
* expressions. The string is split on whitespace and passed to spawn()
|
|
44
|
+
* WITHOUT a shell — no quoting, globbing, expansion, or operators are
|
|
45
|
+
* honoured. Any single (') or double (") quote character in the command
|
|
46
|
+
* is a configuration error: rejected via assertNoShellQuoting with
|
|
47
|
+
* exitCode 2 (task-loader also asserts this at load time so --dry-run
|
|
48
|
+
* catches it; the check here is the chokepoint guarantee for any caller
|
|
49
|
+
* that bypasses the loader).
|
|
50
|
+
*
|
|
51
|
+
* Why reject instead of parse: silently splitting a quoted command on
|
|
52
|
+
* whitespace shreds it ("bash -c \"a && b\"" → spawn('bash',
|
|
53
|
+
* ['-c', '"a', '&&', 'b"']) — bash exits 2, cron mails operator forever).
|
|
54
|
+
* If you need shell features, put them in a script file and invoke the
|
|
55
|
+
* script, or move the bash wrapper to the cron entry.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} command command string from schedules/<id>.json
|
|
58
|
+
* @returns {{ bin: string, args: string[] }}
|
|
59
|
+
* @throws {Error & { exitCode: 2 }} on quoted or empty commands
|
|
60
|
+
*/
|
|
61
|
+
function parseCommand(command) {
|
|
62
|
+
assertNoShellQuoting(command);
|
|
63
|
+
const parts = command.trim().split(/\s+/);
|
|
64
|
+
if (!parts[0]) {
|
|
65
|
+
const e = new Error('command is empty');
|
|
66
|
+
e.exitCode = 2;
|
|
67
|
+
throw e;
|
|
68
|
+
}
|
|
69
|
+
return { bin: parts[0], args: parts.slice(1) };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function defaultSpawner(plan) {
|
|
73
|
+
const { bin, args } = parseCommand(plan.task.command);
|
|
74
|
+
const cwd = resolveWorkdir(plan.task.workingDirectory, plan.repoRoot);
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
const child = spawn(bin, args, { cwd, stdio: 'inherit' });
|
|
77
|
+
child.on('close', (code) => resolve(code ?? 0));
|
|
78
|
+
child.on('error', (err) => {
|
|
79
|
+
const e = new Error(`spawn failed for "${bin}": ${err.message}`);
|
|
80
|
+
e.exitCode = 3;
|
|
81
|
+
reject(e);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function nowIso() {
|
|
87
|
+
return new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function writeAuditRecord(plan, status, exitCode, extra = {}) {
|
|
91
|
+
const record = {
|
|
92
|
+
ts: nowIso(),
|
|
93
|
+
status,
|
|
94
|
+
agentId: plan.agent.id,
|
|
95
|
+
taskId: plan.task.id,
|
|
96
|
+
exitCode,
|
|
97
|
+
...extra,
|
|
98
|
+
};
|
|
99
|
+
await appendFile(plan.auditLogPath, JSON.stringify(record) + '\n', 'utf8');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function execute(plan, {
|
|
103
|
+
spawner = defaultSpawner,
|
|
104
|
+
lockAcquire = acquireLock,
|
|
105
|
+
stderr = process.stderr,
|
|
106
|
+
} = {}) {
|
|
107
|
+
await mkdir(plan.intakeFolderPath, { recursive: true });
|
|
108
|
+
|
|
109
|
+
// Per-task concurrency lock — default-on. Programmatic callers that hand
|
|
110
|
+
// the runner a plan without lockPath still get protection: the chokepoint
|
|
111
|
+
// derives the same per-task path the planner would have chosen.
|
|
112
|
+
const lockPath = plan.lockPath ?? join(plan.repoRoot, '.cache', 'launch', `${plan.task.id}.lock`);
|
|
113
|
+
const lockOptions = {};
|
|
114
|
+
if (Number.isFinite(plan.lockStaleAfterMs) && plan.lockStaleAfterMs > 0) {
|
|
115
|
+
lockOptions.staleAfterMs = plan.lockStaleAfterMs;
|
|
116
|
+
}
|
|
117
|
+
const lock = await lockAcquire(lockPath, lockOptions);
|
|
118
|
+
if (!lock.acquired) {
|
|
119
|
+
// Same-task run already in flight — skip cleanly, spawn nothing.
|
|
120
|
+
await writeAuditRecord(plan, 'noop-locked', 0, { reason: `lock contention: ${lock.reason}` });
|
|
121
|
+
return { status: 'noop-locked', exitCode: 0, reason: lock.reason, lockPath };
|
|
122
|
+
}
|
|
123
|
+
if (lock.stale) {
|
|
124
|
+
stderr.write(
|
|
125
|
+
`[launch] warning: broke stale lock at ${lockPath} ` +
|
|
126
|
+
`(${lock.stale.reason}; pid ${lock.stale.pid ?? 'unknown'}, ` +
|
|
127
|
+
`acquired ${lock.stale.acquiredAt ?? 'unknown'}) — proceeding\n`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
let exitCode;
|
|
133
|
+
try {
|
|
134
|
+
exitCode = await spawner(plan);
|
|
135
|
+
} catch (err) {
|
|
136
|
+
await writeAuditRecord(plan, 'fail-spawn', -1);
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const status = exitCode === 0 ? 'ok' : `fail-exit-${exitCode}`;
|
|
141
|
+
await writeAuditRecord(plan, status, exitCode);
|
|
142
|
+
|
|
143
|
+
return { status, exitCode };
|
|
144
|
+
} finally {
|
|
145
|
+
await lock.release();
|
|
146
|
+
}
|
|
147
|
+
}
|