@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,202 @@
|
|
|
1
|
+
// lock.mjs — shared per-resource process lock (public core API).
|
|
2
|
+
//
|
|
3
|
+
// Two runs of the same scheduled task on the same host MUST NOT overlap:
|
|
4
|
+
// a second concurrent run consumes the same inputs (staging areas, work
|
|
5
|
+
// queues) and produces duplicate effects and git races. This module is
|
|
6
|
+
// the platform-wide primitive for "at most one of me at a time" —
|
|
7
|
+
// consumed default-on by `pa launch`, adoptable by any direct-run tool.
|
|
8
|
+
//
|
|
9
|
+
// Implementation: a JSON PID file at a caller-chosen path. Contents:
|
|
10
|
+
// { "pid": 12345, "acquiredAt": "2026-07-06T12:00:00Z" }
|
|
11
|
+
//
|
|
12
|
+
// Staleness policy (two signals, in order):
|
|
13
|
+
// 1. Dead PID — primary. If the recorded process no longer exists, the
|
|
14
|
+
// lock is stale regardless of age and is broken immediately.
|
|
15
|
+
// 2. Age threshold — backstop. If the recorded process appears alive
|
|
16
|
+
// but the lock is older than `staleAfterMs`, it is broken anyway.
|
|
17
|
+
// This covers PID reuse (the recorded pid now names an unrelated
|
|
18
|
+
// process, so signal 1 reports "alive" forever) and hosts where
|
|
19
|
+
// process liveness cannot be determined reliably.
|
|
20
|
+
//
|
|
21
|
+
// Default threshold: 2 hours (DEFAULT_STALE_AFTER_MS). Rationale: the
|
|
22
|
+
// motivating consumers are scheduled agent sweeps whose legitimate
|
|
23
|
+
// runtimes are routinely tens of minutes (a supervised distillation
|
|
24
|
+
// sweep has been observed at 1288+ seconds); a short window (e.g. the
|
|
25
|
+
// 10 minutes the sync tool uses for its seconds-long git runs) would
|
|
26
|
+
// steal a live lock mid-sweep. 2 hours safely clears observed long runs
|
|
27
|
+
// while still self-healing a pid-reuse wedge within one-to-two ticks of
|
|
28
|
+
// a typical 4-hourly schedule. Callers with faster, well-bounded jobs
|
|
29
|
+
// SHOULD pass a tighter `staleAfterMs`; callers with longer legitimate
|
|
30
|
+
// runtimes MUST raise it.
|
|
31
|
+
//
|
|
32
|
+
// Unreadable lock files: a lock file that exists but cannot be parsed
|
|
33
|
+
// (crash mid-write, manual corruption) can never release itself and its
|
|
34
|
+
// pid cannot be checked. It is treated as held until its file mtime
|
|
35
|
+
// exceeds `staleAfterMs`, then broken. This keeps the age threshold as
|
|
36
|
+
// the universal backstop: no lock state can wedge a schedule forever.
|
|
37
|
+
//
|
|
38
|
+
// Contention contract: `acquire` returns `{ acquired: false, reason }`
|
|
39
|
+
// on live contention — it never throws for "someone else holds it".
|
|
40
|
+
// Callers decide the exit semantics (the platform convention for cron
|
|
41
|
+
// consumers is a logged skip + exit 0, matching sync's `noop-locked`).
|
|
42
|
+
//
|
|
43
|
+
// First-run path: neither the lock file nor its parent directory need
|
|
44
|
+
// exist. A missing directory is created on demand (write → ENOENT →
|
|
45
|
+
// mkdir -p → retry) so the empty state is the common case, not an error.
|
|
46
|
+
//
|
|
47
|
+
// Cross-platform: pure node stdlib. Liveness uses `process.kill(pid, 0)`
|
|
48
|
+
// which works on Windows as well as POSIX; tests inject
|
|
49
|
+
// `isProcessAlive` / `now` / `fs` so no real processes or clocks are
|
|
50
|
+
// needed (tooling standard §8 — dependency injection over mocking).
|
|
51
|
+
|
|
52
|
+
import { readFile, writeFile, unlink, stat, mkdir } from 'node:fs/promises';
|
|
53
|
+
import { dirname } from 'node:path';
|
|
54
|
+
|
|
55
|
+
export const DEFAULT_STALE_AFTER_MS = 2 * 60 * 60 * 1000; // 2 hours
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Acquire a per-resource process lock.
|
|
59
|
+
*
|
|
60
|
+
* @param {string} lockPath Absolute path of the lock file. Convention:
|
|
61
|
+
* one path per protected resource (e.g. `.cache/launch/<taskId>.lock`).
|
|
62
|
+
* @param {object} [opts]
|
|
63
|
+
* @param {number} [opts.staleAfterMs] Age past which a lock is broken
|
|
64
|
+
* even if its pid looks alive. Default: DEFAULT_STALE_AFTER_MS (2 h).
|
|
65
|
+
* @param {() => number} [opts.now] Clock seam for tests.
|
|
66
|
+
* @param {number} [opts.pid] Owner pid recorded in the lock file.
|
|
67
|
+
* @param {(pid: number) => boolean} [opts.isProcessAlive] Liveness seam.
|
|
68
|
+
* @param {object} [opts.fs] fs seam ({ readFile, writeFile, unlink,
|
|
69
|
+
* stat, mkdir }) for tests.
|
|
70
|
+
* @returns {Promise<
|
|
71
|
+
* | { acquired: false, holder: { pid: number|null, acquiredAt: string|null }, reason: string }
|
|
72
|
+
* | { acquired: true, record: { pid: number, acquiredAt: string },
|
|
73
|
+
* stale: null | { pid: number|null, acquiredAt: string|null, reason: 'dead-pid'|'age-exceeded'|'unreadable' },
|
|
74
|
+
* release: () => Promise<void> }
|
|
75
|
+
* >}
|
|
76
|
+
*/
|
|
77
|
+
export async function acquireLock(lockPath, {
|
|
78
|
+
staleAfterMs = DEFAULT_STALE_AFTER_MS,
|
|
79
|
+
now = () => Date.now(),
|
|
80
|
+
pid = process.pid,
|
|
81
|
+
isProcessAlive = defaultIsProcessAlive,
|
|
82
|
+
fs = defaultFs,
|
|
83
|
+
} = {}) {
|
|
84
|
+
// Read the existing lock, if any.
|
|
85
|
+
let raw = null;
|
|
86
|
+
try {
|
|
87
|
+
raw = await fs.readFile(lockPath, 'utf8');
|
|
88
|
+
} catch (err) {
|
|
89
|
+
if (err.code !== 'ENOENT') throw err;
|
|
90
|
+
// No lock file — proceed to acquire (the common first-run case).
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let stale = null;
|
|
94
|
+
if (raw != null) {
|
|
95
|
+
let existing = null;
|
|
96
|
+
try {
|
|
97
|
+
existing = JSON.parse(raw);
|
|
98
|
+
} catch {
|
|
99
|
+
existing = null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (existing && Number.isFinite(existing.pid) && existing.acquiredAt) {
|
|
103
|
+
const ageMs = now() - new Date(existing.acquiredAt).getTime();
|
|
104
|
+
const alive = isProcessAlive(existing.pid);
|
|
105
|
+
if (alive && (!Number.isFinite(ageMs) || ageMs < staleAfterMs)) {
|
|
106
|
+
// Live lock — contention. (A non-finite age means a mangled
|
|
107
|
+
// timestamp; with a live pid we err on the side of holding.)
|
|
108
|
+
return {
|
|
109
|
+
acquired: false,
|
|
110
|
+
holder: { pid: existing.pid, acquiredAt: existing.acquiredAt },
|
|
111
|
+
reason: `held by pid ${existing.pid} since ${existing.acquiredAt}`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
stale = {
|
|
115
|
+
pid: existing.pid,
|
|
116
|
+
acquiredAt: existing.acquiredAt,
|
|
117
|
+
reason: alive ? 'age-exceeded' : 'dead-pid',
|
|
118
|
+
};
|
|
119
|
+
} else {
|
|
120
|
+
// Unparseable lock file — pid can't be checked, so the age
|
|
121
|
+
// threshold (via file mtime) is the only backstop.
|
|
122
|
+
let mtimeMs = null;
|
|
123
|
+
try {
|
|
124
|
+
mtimeMs = (await fs.stat(lockPath)).mtimeMs;
|
|
125
|
+
} catch (err) {
|
|
126
|
+
if (err.code !== 'ENOENT') throw err;
|
|
127
|
+
// Vanished between read and stat — treat as absent.
|
|
128
|
+
}
|
|
129
|
+
if (mtimeMs != null && now() - mtimeMs < staleAfterMs) {
|
|
130
|
+
return {
|
|
131
|
+
acquired: false,
|
|
132
|
+
holder: { pid: null, acquiredAt: null },
|
|
133
|
+
reason: `unreadable lock file at ${lockPath} — treating as held until stale`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (mtimeMs != null) {
|
|
137
|
+
stale = { pid: null, acquiredAt: null, reason: 'unreadable' };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const record = {
|
|
143
|
+
pid,
|
|
144
|
+
acquiredAt: new Date(now()).toISOString().replace(/\.\d+Z$/, 'Z'),
|
|
145
|
+
};
|
|
146
|
+
await writeWithMkdir(fs, lockPath, JSON.stringify(record));
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
acquired: true,
|
|
150
|
+
record,
|
|
151
|
+
stale,
|
|
152
|
+
async release() {
|
|
153
|
+
try {
|
|
154
|
+
// Defensive: only remove if it's still ours. A long-running run
|
|
155
|
+
// whose lock went stale and was stolen by a later run must not
|
|
156
|
+
// delete the later run's lock when it finally exits.
|
|
157
|
+
const currentRaw = await fs.readFile(lockPath, 'utf8');
|
|
158
|
+
const current = JSON.parse(currentRaw);
|
|
159
|
+
if (current.pid === pid && current.acquiredAt === record.acquiredAt) {
|
|
160
|
+
await fs.unlink(lockPath);
|
|
161
|
+
}
|
|
162
|
+
} catch (err) {
|
|
163
|
+
if (err && err.code === 'ENOENT') return;
|
|
164
|
+
if (err instanceof SyntaxError) return; // clobbered — not ours to delete
|
|
165
|
+
throw err;
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function writeWithMkdir(fs, lockPath, content) {
|
|
172
|
+
try {
|
|
173
|
+
await fs.writeFile(lockPath, content);
|
|
174
|
+
} catch (err) {
|
|
175
|
+
if (err.code !== 'ENOENT') throw err;
|
|
176
|
+
// Parent directory missing — first run. Create it and retry once.
|
|
177
|
+
await fs.mkdir(dirname(lockPath), { recursive: true });
|
|
178
|
+
await fs.writeFile(lockPath, content);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function defaultIsProcessAlive(p) {
|
|
183
|
+
try {
|
|
184
|
+
// Signal 0 == "does this process exist and can we signal it?".
|
|
185
|
+
// Throws ESRCH if not. EPERM (someone else's process) also means
|
|
186
|
+
// alive — treated conservatively as alive.
|
|
187
|
+
process.kill(p, 0);
|
|
188
|
+
return true;
|
|
189
|
+
} catch (err) {
|
|
190
|
+
if (err.code === 'ESRCH') return false;
|
|
191
|
+
if (err.code === 'EPERM') return true;
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const defaultFs = {
|
|
197
|
+
readFile,
|
|
198
|
+
writeFile,
|
|
199
|
+
unlink,
|
|
200
|
+
stat,
|
|
201
|
+
mkdir,
|
|
202
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// cli-support.mjs — tiny shared primitives for CLI layer.
|
|
2
|
+
|
|
3
|
+
export class ExitError extends Error {
|
|
4
|
+
constructor(message, code) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.exitCode = code;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function log(msg) {
|
|
11
|
+
process.stderr.write(`[memory-sync] ${msg}\n`);
|
|
12
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// cli.mjs — memory-sync command router.
|
|
2
|
+
//
|
|
3
|
+
// Preserves the pull/push verb contract from the shell implementation and
|
|
4
|
+
// maps domain errors to the standard exit-code ladder (see README §Exit codes).
|
|
5
|
+
|
|
6
|
+
import { pull } from './commands/pull.mjs';
|
|
7
|
+
import { push } from './commands/push.mjs';
|
|
8
|
+
import { ExitError, log } from './cli-support.mjs';
|
|
9
|
+
|
|
10
|
+
const USAGE = `Usage: memory-sync <pull|push|--help>
|
|
11
|
+
pull Mirror repo memory dir -> local Claude Code cache
|
|
12
|
+
push Mirror local Claude Code cache -> repo memory dir
|
|
13
|
+
-h, --help Show this help
|
|
14
|
+
Exit codes: 0 ok, 1 usage, 2 source dir missing, 3 io failure`;
|
|
15
|
+
|
|
16
|
+
export async function main(argv) {
|
|
17
|
+
const code = await run(argv);
|
|
18
|
+
process.exit(code ?? 0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Exported for tests: returns an exit code rather than calling process.exit.
|
|
23
|
+
*/
|
|
24
|
+
export async function run(argv, opts = {}) {
|
|
25
|
+
const mode = argv[0];
|
|
26
|
+
if (mode === '-h' || mode === '--help' || mode === 'help') {
|
|
27
|
+
process.stdout.write(USAGE + '\n');
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
if (mode == null || mode === '') {
|
|
31
|
+
process.stderr.write(USAGE + '\n');
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
if (mode !== 'pull' && mode !== 'push') {
|
|
35
|
+
log(`unknown verb: ${mode}`);
|
|
36
|
+
process.stderr.write(USAGE + '\n');
|
|
37
|
+
return 1;
|
|
38
|
+
}
|
|
39
|
+
if (argv.length > 1) {
|
|
40
|
+
log(`unexpected arguments: ${argv.slice(1).join(' ')}`);
|
|
41
|
+
process.stderr.write(USAGE + '\n');
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
if (mode === 'pull') return await pull(opts);
|
|
47
|
+
return await push(opts);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if (err instanceof ExitError) {
|
|
50
|
+
log(err.message);
|
|
51
|
+
return err.exitCode;
|
|
52
|
+
}
|
|
53
|
+
log(`internal error: ${err.message || err}`);
|
|
54
|
+
return 3;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// commands/pull.mjs — mirror repo memory dir -> local Claude Code cache.
|
|
2
|
+
|
|
3
|
+
import { stat } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { findRepoRoot, cacheDirFor } from '../repo.mjs';
|
|
6
|
+
import { mirror } from '../mirror.mjs';
|
|
7
|
+
import { ExitError, log } from '../cli-support.mjs';
|
|
8
|
+
|
|
9
|
+
async function isDir(p) {
|
|
10
|
+
try {
|
|
11
|
+
const s = await stat(p);
|
|
12
|
+
return s.isDirectory();
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function pull({ cwd = process.cwd() } = {}) {
|
|
19
|
+
const { root, worktree } = await findRepoRoot(cwd);
|
|
20
|
+
if (!root) throw new ExitError('not inside a git repo', 2);
|
|
21
|
+
if (worktree) {
|
|
22
|
+
log('in a git worktree — skipping pull');
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const repoDir = join(root, 'operator', 'memory');
|
|
27
|
+
const cacheDir = cacheDirFor(root);
|
|
28
|
+
|
|
29
|
+
if (!(await isDir(repoDir))) {
|
|
30
|
+
throw new ExitError(`repo memory dir missing: ${repoDir}`, 2);
|
|
31
|
+
}
|
|
32
|
+
if (!(await isDir(cacheDir))) {
|
|
33
|
+
throw new ExitError(`local cache missing: ${cacheDir}`, 2);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let n;
|
|
37
|
+
try {
|
|
38
|
+
n = await mirror(repoDir, cacheDir);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
throw new ExitError(`mirror failed: ${err.message || err}`, 3);
|
|
41
|
+
}
|
|
42
|
+
log(`pull: ${n} files mirrored from repo to cache`);
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// commands/push.mjs — mirror local Claude Code cache -> repo memory dir.
|
|
2
|
+
|
|
3
|
+
import { mkdir, stat } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { findRepoRoot, cacheDirFor } from '../repo.mjs';
|
|
6
|
+
import { mirror } from '../mirror.mjs';
|
|
7
|
+
import { ExitError, log } from '../cli-support.mjs';
|
|
8
|
+
|
|
9
|
+
async function isDir(p) {
|
|
10
|
+
try {
|
|
11
|
+
const s = await stat(p);
|
|
12
|
+
return s.isDirectory();
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function push({ cwd = process.cwd() } = {}) {
|
|
19
|
+
const { root, worktree } = await findRepoRoot(cwd);
|
|
20
|
+
if (!root) throw new ExitError('not inside a git repo', 2);
|
|
21
|
+
if (worktree) {
|
|
22
|
+
log('in a git worktree — skipping push');
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const repoDir = join(root, 'operator', 'memory');
|
|
27
|
+
const cacheDir = cacheDirFor(root);
|
|
28
|
+
|
|
29
|
+
if (!(await isDir(cacheDir))) {
|
|
30
|
+
throw new ExitError(`local cache missing: ${cacheDir}`, 2);
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
await mkdir(repoDir, { recursive: true });
|
|
34
|
+
} catch (err) {
|
|
35
|
+
throw new ExitError(`cannot create repo memory dir: ${err.message || err}`, 3);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let n;
|
|
39
|
+
try {
|
|
40
|
+
n = await mirror(cacheDir, repoDir);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
throw new ExitError(`mirror failed: ${err.message || err}`, 3);
|
|
43
|
+
}
|
|
44
|
+
log(`push: ${n} files mirrored from cache to repo`);
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// mirror.mjs — recursive directory mirror with deletion semantics.
|
|
2
|
+
//
|
|
3
|
+
// This is the single chokepoint for filesystem mutation in the tool.
|
|
4
|
+
// `mirror(src, dst)` makes `dst` a byte-for-byte copy of `src`:
|
|
5
|
+
//
|
|
6
|
+
// 1. every regular file under `src` is copied to `dst` (mkdir -p as needed),
|
|
7
|
+
// preserving mtime where possible
|
|
8
|
+
// 2. every file under `dst` that does not exist under `src` is removed
|
|
9
|
+
// 3. empty directories under `dst` left behind after deletions are pruned
|
|
10
|
+
//
|
|
11
|
+
// Errors in this module map to the tool's exit code 3 (IO failure). The
|
|
12
|
+
// caller is responsible for verifying `src` exists — that is a different
|
|
13
|
+
// class of failure (exit 2) and lives in the command layer.
|
|
14
|
+
|
|
15
|
+
import { mkdir, readdir, copyFile, stat, utimes, rm, rmdir } from 'node:fs/promises';
|
|
16
|
+
import { join, dirname, relative } from 'node:path';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Recursively list every regular file under `root`, returning paths relative
|
|
20
|
+
* to `root` (always using forward slashes for stable comparison).
|
|
21
|
+
*/
|
|
22
|
+
async function listFiles(root) {
|
|
23
|
+
const out = [];
|
|
24
|
+
async function walk(dir) {
|
|
25
|
+
let entries;
|
|
26
|
+
try {
|
|
27
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
28
|
+
} catch (err) {
|
|
29
|
+
if (err.code === 'ENOENT') return;
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
for (const ent of entries) {
|
|
33
|
+
const full = join(dir, ent.name);
|
|
34
|
+
if (ent.isDirectory()) {
|
|
35
|
+
await walk(full);
|
|
36
|
+
} else if (ent.isFile()) {
|
|
37
|
+
out.push(relative(root, full).split('\\').join('/'));
|
|
38
|
+
}
|
|
39
|
+
// symlinks / sockets / etc. are intentionally skipped — matches the
|
|
40
|
+
// shell implementation which used `find -type f`
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
await walk(root);
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Remove empty directories under `root` from the bottom up. The `root`
|
|
49
|
+
* itself is never removed.
|
|
50
|
+
*/
|
|
51
|
+
async function pruneEmptyDirs(root) {
|
|
52
|
+
async function walk(dir) {
|
|
53
|
+
let entries;
|
|
54
|
+
try {
|
|
55
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
56
|
+
} catch (err) {
|
|
57
|
+
if (err.code === 'ENOENT') return;
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
for (const ent of entries) {
|
|
61
|
+
if (ent.isDirectory()) await walk(join(dir, ent.name));
|
|
62
|
+
}
|
|
63
|
+
if (dir === root) return;
|
|
64
|
+
try {
|
|
65
|
+
const remaining = await readdir(dir);
|
|
66
|
+
if (remaining.length === 0) await rmdir(dir);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
if (err.code !== 'ENOENT' && err.code !== 'ENOTEMPTY') throw err;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
await walk(root);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Mirror src -> dst. See module header for full semantics.
|
|
76
|
+
* Returns the number of files present in `dst` after the mirror completes
|
|
77
|
+
* (which equals the count of files in `src`).
|
|
78
|
+
*/
|
|
79
|
+
export async function mirror(src, dst) {
|
|
80
|
+
await mkdir(dst, { recursive: true });
|
|
81
|
+
|
|
82
|
+
const srcFiles = await listFiles(src);
|
|
83
|
+
const srcSet = new Set(srcFiles);
|
|
84
|
+
|
|
85
|
+
// Copy everything from src to dst. We copy unconditionally rather than
|
|
86
|
+
// comparing mtimes: the source wins, full stop. This matches `rsync -a
|
|
87
|
+
// --delete` behaviour the shell version used.
|
|
88
|
+
for (const rel of srcFiles) {
|
|
89
|
+
const from = join(src, rel);
|
|
90
|
+
const to = join(dst, rel);
|
|
91
|
+
await mkdir(dirname(to), { recursive: true });
|
|
92
|
+
await copyFile(from, to);
|
|
93
|
+
// Preserve mtime and atime from source so tooling that relies on
|
|
94
|
+
// timestamps (rsync's -a behaviour) stays consistent.
|
|
95
|
+
try {
|
|
96
|
+
const s = await stat(from);
|
|
97
|
+
await utimes(to, s.atime, s.mtime);
|
|
98
|
+
} catch {
|
|
99
|
+
// utimes can fail on some filesystems; not fatal.
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Delete anything in dst that isn't in src.
|
|
104
|
+
const dstFiles = await listFiles(dst);
|
|
105
|
+
for (const rel of dstFiles) {
|
|
106
|
+
if (!srcSet.has(rel)) {
|
|
107
|
+
await rm(join(dst, rel), { force: true });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
await pruneEmptyDirs(dst);
|
|
112
|
+
|
|
113
|
+
return srcFiles.length;
|
|
114
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// repo.mjs — repo root discovery and cache-dir derivation.
|
|
2
|
+
//
|
|
3
|
+
// Pure helpers, no I/O side effects beyond `stat`-class lookups. Mirrors the
|
|
4
|
+
// semantics of the original sync.sh:
|
|
5
|
+
//
|
|
6
|
+
// - findRepoRoot() walks up from a start dir looking for a `.git` directory.
|
|
7
|
+
// If it finds a `.git` *file* first (i.e. this is a worktree), it reports
|
|
8
|
+
// that via the `worktree` flag so the caller can skip cleanly.
|
|
9
|
+
// - cacheDirFor(repo) sanitises the repo path by replacing '/' with '-' and
|
|
10
|
+
// composes `$HOME/.claude/projects/<sanitised>/memory`. This matches the
|
|
11
|
+
// path Claude Code itself uses for its per-project memory cache.
|
|
12
|
+
|
|
13
|
+
import { stat } from 'node:fs/promises';
|
|
14
|
+
import { dirname, join } from 'node:path';
|
|
15
|
+
import { homedir } from 'node:os';
|
|
16
|
+
|
|
17
|
+
async function pathKind(p) {
|
|
18
|
+
try {
|
|
19
|
+
const s = await stat(p);
|
|
20
|
+
if (s.isDirectory()) return 'dir';
|
|
21
|
+
if (s.isFile()) return 'file';
|
|
22
|
+
return 'other';
|
|
23
|
+
} catch {
|
|
24
|
+
return 'missing';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Walk up from `start` looking for a `.git` entry.
|
|
30
|
+
*
|
|
31
|
+
* Returns `{ root, worktree }` where:
|
|
32
|
+
* - `root` is the directory containing the `.git` entry (or null if none)
|
|
33
|
+
* - `worktree` is true when the `.git` entry is a regular file (git worktree)
|
|
34
|
+
* and false when it is a directory (primary checkout)
|
|
35
|
+
*
|
|
36
|
+
* If nothing is found before the filesystem root, returns `{ root: null, worktree: false }`.
|
|
37
|
+
*/
|
|
38
|
+
export async function findRepoRoot(start) {
|
|
39
|
+
let d = start;
|
|
40
|
+
// Guard against infinite loops on malformed paths
|
|
41
|
+
while (d && d !== dirname(d)) {
|
|
42
|
+
const gitPath = join(d, '.git');
|
|
43
|
+
const kind = await pathKind(gitPath);
|
|
44
|
+
if (kind === 'dir') return { root: d, worktree: false };
|
|
45
|
+
if (kind === 'file') return { root: d, worktree: true };
|
|
46
|
+
d = dirname(d);
|
|
47
|
+
}
|
|
48
|
+
return { root: null, worktree: false };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Given a repository absolute path, return the path of the Claude Code
|
|
53
|
+
* per-project memory cache directory for that repo.
|
|
54
|
+
*
|
|
55
|
+
* The cache lives under `$HOME/.claude/projects/<sanitised>/memory` where
|
|
56
|
+
* `<sanitised>` is the repo path with every '/' (or platform separator)
|
|
57
|
+
* replaced by '-'. This matches Claude Code's own sanitisation.
|
|
58
|
+
*/
|
|
59
|
+
export function cacheDirFor(repoPath, { home = homedir() } = {}) {
|
|
60
|
+
// Sanitise both '/' and '\\' to '-' so we produce a stable key regardless
|
|
61
|
+
// of the host platform's path-separator conventions. Claude Code itself
|
|
62
|
+
// uses this sanitisation to namespace per-project caches.
|
|
63
|
+
const sanitised = repoPath.replace(/[\\/]/g, '-');
|
|
64
|
+
return join(home, '.claude', 'projects', sanitised, 'memory');
|
|
65
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* detect.mjs -- Detect available scheduling backends
|
|
4
|
+
*
|
|
5
|
+
* Outputs a JSON report of OS type and available scheduling facilities.
|
|
6
|
+
* Used by the PA to determine which backend to offer during setup.
|
|
7
|
+
*
|
|
8
|
+
* Usage: node node_modules/@stravica-ai/engineering-pa-core/src/scheduler/detect.mjs
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execSync } from 'node:child_process';
|
|
12
|
+
import { realpathSync } from 'node:fs';
|
|
13
|
+
import { resolve } from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Check whether a command exists on this system.
|
|
18
|
+
* @param {string} cmd
|
|
19
|
+
* @returns {boolean}
|
|
20
|
+
*/
|
|
21
|
+
function commandExists(cmd) {
|
|
22
|
+
try {
|
|
23
|
+
const check = process.platform === 'win32'
|
|
24
|
+
? `where ${cmd} 2>nul`
|
|
25
|
+
: `command -v ${cmd} 2>/dev/null`;
|
|
26
|
+
execSync(check, { stdio: 'pipe' });
|
|
27
|
+
return true;
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Detect OS and available scheduling backends.
|
|
35
|
+
* @returns {{ os: string, available: Array<{ backend: string, recommended: boolean, notes: string }> }}
|
|
36
|
+
*/
|
|
37
|
+
export function detect() {
|
|
38
|
+
const os = process.platform === 'darwin' ? 'darwin'
|
|
39
|
+
: process.platform === 'linux' ? 'linux'
|
|
40
|
+
: process.platform === 'win32' ? 'win32'
|
|
41
|
+
: 'unknown';
|
|
42
|
+
|
|
43
|
+
const available = [];
|
|
44
|
+
let hasSystemd = false;
|
|
45
|
+
|
|
46
|
+
// launchd (macOS only)
|
|
47
|
+
if (os === 'darwin' && commandExists('launchctl')) {
|
|
48
|
+
available.push({ backend: 'launchd', recommended: true, notes: 'Native macOS, survives reboots' });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// systemd (Linux only)
|
|
52
|
+
if (os === 'linux' && commandExists('systemctl')) {
|
|
53
|
+
try {
|
|
54
|
+
execSync('systemctl --user status', { stdio: 'pipe' });
|
|
55
|
+
available.push({ backend: 'systemd', recommended: true, notes: 'Native Linux, survives reboots' });
|
|
56
|
+
hasSystemd = true;
|
|
57
|
+
} catch {
|
|
58
|
+
// systemctl exists but user-level not available — skip
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// cron (macOS and Linux)
|
|
63
|
+
if (commandExists('crontab')) {
|
|
64
|
+
if (os === 'darwin') {
|
|
65
|
+
available.push({ backend: 'cron', recommended: false, notes: 'Available but launchd preferred on macOS' });
|
|
66
|
+
} else if (os === 'linux') {
|
|
67
|
+
if (hasSystemd) {
|
|
68
|
+
available.push({ backend: 'cron', recommended: false, notes: 'Available but systemd preferred on Linux' });
|
|
69
|
+
} else {
|
|
70
|
+
available.push({ backend: 'cron', recommended: true, notes: 'Classic scheduler, widely available' });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// schtasks (Windows)
|
|
76
|
+
if (os === 'win32' && commandExists('schtasks.exe')) {
|
|
77
|
+
available.push({ backend: 'schtasks', recommended: true, notes: 'Native Windows Task Scheduler' });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Docker (any OS)
|
|
81
|
+
if (commandExists('docker')) {
|
|
82
|
+
try {
|
|
83
|
+
execSync('docker info', { stdio: 'pipe' });
|
|
84
|
+
available.push({ backend: 'docker', recommended: false, notes: 'Docker detected and running' });
|
|
85
|
+
} catch {
|
|
86
|
+
// docker exists but daemon not running
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { os, available };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── CLI entry point ──────────────────────────────────────────────────
|
|
94
|
+
//
|
|
95
|
+
// BUG 3 fix — see install.mjs for the full rationale: realpath both sides
|
|
96
|
+
// instead of the previous realpath-vs-symlink endsWith comparison, which
|
|
97
|
+
// silently no-op'd under pnpm.
|
|
98
|
+
|
|
99
|
+
function isDirectlyInvoked() {
|
|
100
|
+
if (!process.argv[1]) return false;
|
|
101
|
+
try {
|
|
102
|
+
return fileURLToPath(import.meta.url) === realpathSync(resolve(process.argv[1]));
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (isDirectlyInvoked()) {
|
|
109
|
+
const result = detect();
|
|
110
|
+
process.stdout.write(JSON.stringify(result) + '\n');
|
|
111
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* host.mjs -- shared hostname normalisation + matching for schedule host
|
|
3
|
+
* enforcement.
|
|
4
|
+
*
|
|
5
|
+
* Same short-name-before-first-dot algorithm used by the estate's
|
|
6
|
+
* device-resolution tooling (operator/extensions/tools/library-sync's
|
|
7
|
+
* resolveDeviceId) — kept deliberately simple and dependency-free so this
|
|
8
|
+
* module has zero coupling to the registry data model: schedule host
|
|
9
|
+
* enforcement only needs "does this string match the current machine",
|
|
10
|
+
* not "which registry device am I".
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { hostname as osHostname } from 'node:os';
|
|
14
|
+
|
|
15
|
+
/** The portion of a hostname before its first "." — case preserved. */
|
|
16
|
+
export function shortHost(h) {
|
|
17
|
+
if (typeof h !== 'string' || h.length === 0) return '';
|
|
18
|
+
return h.split('.')[0];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Does `scheduleHost` (a schedule's declared `host` field) match the
|
|
23
|
+
* current machine? Absent/empty `scheduleHost` is treated as "no
|
|
24
|
+
* constraint declared" -> always matches (callers surface that as a
|
|
25
|
+
* warning, not a refusal — see evaluateHostGate in install.mjs).
|
|
26
|
+
*
|
|
27
|
+
* Match order: exact string equality, then short-name-before-first-dot
|
|
28
|
+
* equality (so `smarthome01` matches `smarthome01.local` and vice versa).
|
|
29
|
+
*
|
|
30
|
+
* @param {string|null|undefined} scheduleHost
|
|
31
|
+
* @param {string} [currentHost] - defaults to the real os.hostname()
|
|
32
|
+
* @returns {boolean}
|
|
33
|
+
*/
|
|
34
|
+
export function hostMatches(scheduleHost, currentHost = osHostname()) {
|
|
35
|
+
if (scheduleHost == null || scheduleHost === '') return true;
|
|
36
|
+
if (scheduleHost === currentHost) return true;
|
|
37
|
+
return shortHost(scheduleHost) === shortHost(currentHost);
|
|
38
|
+
}
|