@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,149 @@
|
|
|
1
|
+
// sync — git operations.
|
|
2
|
+
//
|
|
3
|
+
// Thin wrappers around the specific git invocations sync performs. Every
|
|
4
|
+
// function delegates spawning to the runner chokepoint — nothing here
|
|
5
|
+
// touches the outside world directly.
|
|
6
|
+
//
|
|
7
|
+
// The set is intentionally minimal: status, branch, ahead/behind, fetch,
|
|
8
|
+
// pull --rebase, rebase --abort, add -A, commit, push. Sync is the SOLE
|
|
9
|
+
// git writer on a host running HQ crons; if we ever need a new git
|
|
10
|
+
// operation here, it should be considered carefully — a wider surface
|
|
11
|
+
// invites the kind of race this tool exists to eliminate.
|
|
12
|
+
|
|
13
|
+
// `--untracked-files=all` is load-bearing, not cosmetic: plain
|
|
14
|
+
// `git status --porcelain` collapses a WHOLLY untracked directory to a
|
|
15
|
+
// single ancestor line (e.g. `?? agents/` when nothing under `agents/`
|
|
16
|
+
// is tracked), rather than listing each untracked file individually.
|
|
17
|
+
// registry-scope.mjs's exclusion check is a `startsWith` match against
|
|
18
|
+
// mount `to` prefixes (e.g. `agents/dave/`) — a collapsed `agents/` line
|
|
19
|
+
// does not start with `agents/dave/`, so it survives the exclusion
|
|
20
|
+
// filter and everything under it (mount-owned content included) gets
|
|
21
|
+
// staged. This only bites when the mount target has no tracked anchor
|
|
22
|
+
// file anywhere in its ancestry (fresh onboarding, no prior commits
|
|
23
|
+
// under that subtree) — exactly the case a brand-new mount hits. `=all`
|
|
24
|
+
// makes git enumerate every untracked file individually, so each path
|
|
25
|
+
// is checked against the exclusion prefixes on its own merits.
|
|
26
|
+
export async function statusPorcelain(runner, { cwd }) {
|
|
27
|
+
return runner.spawn('git', ['status', '--porcelain', '--untracked-files=all'], { cwd });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function currentBranch(runner, { cwd }) {
|
|
31
|
+
const r = await runner.spawn('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd });
|
|
32
|
+
return r.stdout.trim();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Are we ahead of / behind the upstream tracking branch?
|
|
36
|
+
//
|
|
37
|
+
// Returns { ahead, behind }. Either may be NaN if upstream is missing —
|
|
38
|
+
// callers treat NaN ahead as "0 ahead" (nothing to push) and NaN behind as
|
|
39
|
+
// "no upstream tracking, do not pull".
|
|
40
|
+
export async function aheadBehind(runner, { cwd }) {
|
|
41
|
+
const r = await runner.spawn(
|
|
42
|
+
'git',
|
|
43
|
+
['rev-list', '--left-right', '--count', 'HEAD...@{u}'],
|
|
44
|
+
{ cwd },
|
|
45
|
+
);
|
|
46
|
+
if (r.code !== 0) {
|
|
47
|
+
return { ahead: NaN, behind: NaN };
|
|
48
|
+
}
|
|
49
|
+
const parts = r.stdout.trim().split(/\s+/);
|
|
50
|
+
const ahead = Number.parseInt(parts[0], 10);
|
|
51
|
+
const behind = Number.parseInt(parts[1], 10);
|
|
52
|
+
return { ahead, behind };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function fetchOrigin(runner, { cwd }) {
|
|
56
|
+
return runner.spawn('git', ['fetch', 'origin'], { cwd });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function pullRebase(runner, { cwd, branch }) {
|
|
60
|
+
return runner.spawn('git', ['pull', '--rebase', '--quiet', 'origin', branch], { cwd });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function rebaseAbort(runner, { cwd }) {
|
|
64
|
+
return runner.spawn('git', ['rebase', '--abort'], { cwd });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function addAll(runner, { cwd }) {
|
|
68
|
+
return runner.spawn('git', ['add', '-A'], { cwd });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function hasStagedChanges(runner, { cwd }) {
|
|
72
|
+
const r = await runner.spawn('git', ['diff', '--cached', '--quiet'], { cwd });
|
|
73
|
+
return r.code !== 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Commit with a title + optional multi-paragraph body. Passes message
|
|
77
|
+
// through argv (-m / -m) — runner spawns with shell:false so embedded
|
|
78
|
+
// quotes / newlines are safe without further escaping.
|
|
79
|
+
export async function commit(runner, { cwd, title, body }) {
|
|
80
|
+
const args = ['commit', '-m', title];
|
|
81
|
+
if (body && body.length > 0) {
|
|
82
|
+
args.push('-m', body);
|
|
83
|
+
}
|
|
84
|
+
return runner.spawn('git', args, { cwd });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function push(runner, { cwd }) {
|
|
88
|
+
return runner.spawn('git', ['push'], { cwd });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Parse `git status --porcelain` output for paths currently in conflict.
|
|
92
|
+
// Git marks unmerged entries with an XY code from a fixed set (see
|
|
93
|
+
// git-status(1) `Short Format` — "Unmerged entries have the following
|
|
94
|
+
// second column"): DD, AU, UD, UA, DU, AA, UU. Anything else is a normal
|
|
95
|
+
// modification (M ), untracked (??), etc.
|
|
96
|
+
export async function listConflictedPaths(runner, { cwd }) {
|
|
97
|
+
const r = await runner.spawn('git', ['status', '--porcelain'], { cwd });
|
|
98
|
+
if (r.code !== 0) {
|
|
99
|
+
return { code: r.code, stderr: r.stderr, paths: [] };
|
|
100
|
+
}
|
|
101
|
+
const paths = [];
|
|
102
|
+
for (const line of r.stdout.split('\n')) {
|
|
103
|
+
// Porcelain v1 line: `XY <path>` where XY is exactly two chars
|
|
104
|
+
// followed by a space. Renamed entries use `XY <old> -> <new>` — the
|
|
105
|
+
// conflict codes below never apply to renames, so we treat the tail
|
|
106
|
+
// as the path verbatim.
|
|
107
|
+
if (line.length < 4) continue;
|
|
108
|
+
const xy = line.slice(0, 2);
|
|
109
|
+
if (xy === 'DD' || xy === 'AU' || xy === 'UD' || xy === 'UA'
|
|
110
|
+
|| xy === 'DU' || xy === 'AA' || xy === 'UU') {
|
|
111
|
+
paths.push(line.slice(3));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return { code: 0, stderr: '', paths };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Take upstream's version of `paths` during a rebase.
|
|
118
|
+
//
|
|
119
|
+
// GOTCHA — rebase inverts --ours / --theirs vs merge. During a rebase,
|
|
120
|
+
// `--ours` refers to the branch we are rebasing ONTO (upstream, i.e.
|
|
121
|
+
// origin's version) and `--theirs` refers to the branch that holds the
|
|
122
|
+
// commit being replayed (our local work). This is the opposite of merge,
|
|
123
|
+
// where `--ours` is the current branch. Since we want to keep origin's
|
|
124
|
+
// version and drop our fresh regeneration, we pass `--ours`. See the
|
|
125
|
+
// note in git-checkout(1): "Note that during git rebase and git pull
|
|
126
|
+
// --rebase, ours and theirs may appear swapped."
|
|
127
|
+
export async function checkoutOursDuringRebase(runner, { cwd, paths }) {
|
|
128
|
+
if (paths.length === 0) return { code: 0, stdout: '', stderr: '' };
|
|
129
|
+
return runner.spawn('git', ['checkout', '--ours', '--', ...paths], { cwd });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Stage specific paths after conflict resolution. Uses `git add --` to
|
|
133
|
+
// stop any leading `-`-prefixed path being treated as a flag.
|
|
134
|
+
export async function addPaths(runner, { cwd, paths }) {
|
|
135
|
+
if (paths.length === 0) return { code: 0, stdout: '', stderr: '' };
|
|
136
|
+
return runner.spawn('git', ['add', '--', ...paths], { cwd });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Continue an in-progress rebase after conflict resolution. `git rebase
|
|
140
|
+
// --continue` may want to open $EDITOR to confirm the message of the
|
|
141
|
+
// replayed commit; forcing GIT_EDITOR=true (the `true` binary, exit 0)
|
|
142
|
+
// makes it accept the existing message unmodified and continue.
|
|
143
|
+
export async function rebaseContinue(runner, { cwd }) {
|
|
144
|
+
return runner.spawn(
|
|
145
|
+
'git',
|
|
146
|
+
['rebase', '--continue'],
|
|
147
|
+
{ cwd, env: { ...process.env, GIT_EDITOR: 'true' } },
|
|
148
|
+
);
|
|
149
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// sync — process lock.
|
|
2
|
+
//
|
|
3
|
+
// Two sync runs on the same host MUST NOT overlap. A second instance
|
|
4
|
+
// trying to push while the first is mid-commit would just hit the same
|
|
5
|
+
// remote-rejected → rebase-retry path; on a slow host it could land in a
|
|
6
|
+
// loop. Cheaper to short-circuit at start.
|
|
7
|
+
//
|
|
8
|
+
// Implementation: delegates to the shared core lock utility
|
|
9
|
+
// (`@stravica-ai/engineering-pa-core` → `acquireLock`, see
|
|
10
|
+
// packages/core/docs/lock.md) with sync's own staleness window: 10
|
|
11
|
+
// minutes. The core default (2 hours) is sized for long agent sweeps;
|
|
12
|
+
// sync runs are seconds long on a 30-minute cron tick, so a 10-minute
|
|
13
|
+
// window is a safe, much faster self-heal for a wedged run. On stale,
|
|
14
|
+
// the lock is stolen; on contention with a live PID the caller exits `0`
|
|
15
|
+
// with status `noop-locked` — the next tick will retry.
|
|
16
|
+
//
|
|
17
|
+
// `acquire` keeps the historical sync signature and return shape
|
|
18
|
+
// ({ acquired, reason } | { acquired, record, release }); tests inject
|
|
19
|
+
// `now()`, `isProcessAlive`, and a fake fs exactly as before.
|
|
20
|
+
|
|
21
|
+
// Subpath import (not the bare package) — the engineering-pa dist bundle
|
|
22
|
+
// rewrites cross-package import strings to relative paths, and a bare
|
|
23
|
+
// package import would rewrite to a directory path ESM cannot resolve.
|
|
24
|
+
import { acquireLock } from '../../../engineering-pa-core/src/lock/lock.mjs';
|
|
25
|
+
|
|
26
|
+
const STALE_AFTER_MS = 10 * 60 * 1000; // 10 minutes
|
|
27
|
+
|
|
28
|
+
export async function acquire(lockPath, options = {}) {
|
|
29
|
+
return acquireLock(lockPath, { staleAfterMs: STALE_AFTER_MS, ...options });
|
|
30
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// sync — regenerated-artifacts allowlist for auto-conflict-resolution.
|
|
2
|
+
//
|
|
3
|
+
// The problem this solves: on a multi-host HQ deploy, distillation runs
|
|
4
|
+
// on more than one host and regenerates the same knowledge-artifact files
|
|
5
|
+
// (`.graph.json`, `domains.json`, `profile.md`, auto-classified system
|
|
6
|
+
// pages, …). The output is byte-different across hosts (timestamps,
|
|
7
|
+
// map-key ordering, whitespace) but semantically equivalent — every host
|
|
8
|
+
// gets the same information, just written slightly differently. When two
|
|
9
|
+
// hosts push their regenerations within the same cron window, sync's
|
|
10
|
+
// `pull --rebase` conflicts on those files, exits `fail-rebase`, and
|
|
11
|
+
// wedges the cron until a human `git reset --hard origin/main`s the loser.
|
|
12
|
+
//
|
|
13
|
+
// Fix: when a rebase conflicts AND every conflicted path is in the
|
|
14
|
+
// allowlist below, sync takes origin's version, continues the rebase, and
|
|
15
|
+
// pushes. The regeneration we discarded will land in origin naturally on
|
|
16
|
+
// the next distill cycle — that cycle will fold in whatever staging
|
|
17
|
+
// observations we contributed AND whatever the other host contributed.
|
|
18
|
+
// Nothing is lost.
|
|
19
|
+
//
|
|
20
|
+
// Conflicts on any path outside the allowlist still fail-rebase exit 2 —
|
|
21
|
+
// real conflicts must surface for a human to resolve.
|
|
22
|
+
//
|
|
23
|
+
// Extension: repos can add project-specific regenerable paths via a
|
|
24
|
+
// `.sync-config.json` at the repo root:
|
|
25
|
+
// { "regeneratedArtifacts": ["path/to/generated/**"] }
|
|
26
|
+
// Entries are additive to the built-in defaults; unknown fields are
|
|
27
|
+
// ignored so future config additions do not need this module updated.
|
|
28
|
+
|
|
29
|
+
import { readFile } from 'node:fs/promises';
|
|
30
|
+
import { join, matchesGlob } from 'node:path';
|
|
31
|
+
|
|
32
|
+
// Files/globs that distillation regenerates each cycle. Any rebase
|
|
33
|
+
// conflict on these paths is a byte-race between two hosts' regenerations
|
|
34
|
+
// and can safely be auto-resolved by taking origin's version. Paths are
|
|
35
|
+
// relative to the repo root. Globs use POSIX-style `*` (single segment)
|
|
36
|
+
// and `**` (any number of segments) as understood by `path.matchesGlob`
|
|
37
|
+
// (stable in Node ≥ 22.5).
|
|
38
|
+
export const DEFAULT_REGENERATED_ARTIFACTS = Object.freeze([
|
|
39
|
+
'operator/knowledge/.graph.json',
|
|
40
|
+
'operator/knowledge/.backlinks.json',
|
|
41
|
+
'operator/knowledge/domains.json',
|
|
42
|
+
'operator/profile/profile.md',
|
|
43
|
+
'operator/profile/operator.json',
|
|
44
|
+
'operator/knowledge/systems/web-properties/*.md',
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
const CONFIG_FILENAME = '.sync-config.json';
|
|
48
|
+
|
|
49
|
+
// Load the effective allowlist: defaults ∪ any operator-supplied globs
|
|
50
|
+
// from `<repoRoot>/.sync-config.json`. Failure modes are non-fatal:
|
|
51
|
+
// - no file → defaults only, source = 'defaults'
|
|
52
|
+
// - unreadable file → defaults only, source = 'defaults', warning set
|
|
53
|
+
// - malformed JSON → defaults only, source = 'defaults', warning set
|
|
54
|
+
// - malformed field → defaults only, source = 'defaults' (silently
|
|
55
|
+
// drops non-string entries; array must be array)
|
|
56
|
+
// Callers should log the returned `warning` (if any) so a broken config
|
|
57
|
+
// is visible without wedging the cron.
|
|
58
|
+
export async function loadRegeneratedArtifacts({ repoRoot, fs = { readFile } } = {}) {
|
|
59
|
+
const defaults = [...DEFAULT_REGENERATED_ARTIFACTS];
|
|
60
|
+
const result = {
|
|
61
|
+
patterns: defaults,
|
|
62
|
+
source: 'defaults',
|
|
63
|
+
configPath: null,
|
|
64
|
+
added: 0,
|
|
65
|
+
warning: null,
|
|
66
|
+
};
|
|
67
|
+
if (!repoRoot) return result;
|
|
68
|
+
const configPath = join(repoRoot, CONFIG_FILENAME);
|
|
69
|
+
result.configPath = configPath;
|
|
70
|
+
let raw;
|
|
71
|
+
try {
|
|
72
|
+
raw = await fs.readFile(configPath, 'utf8');
|
|
73
|
+
} catch (err) {
|
|
74
|
+
if (err.code === 'ENOENT') return result;
|
|
75
|
+
result.warning = `read failed: ${err.message}`;
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
let parsed;
|
|
79
|
+
try {
|
|
80
|
+
parsed = JSON.parse(raw);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
result.warning = `parse failed: ${err.message}`;
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
const extra = Array.isArray(parsed?.regeneratedArtifacts)
|
|
86
|
+
? parsed.regeneratedArtifacts.filter((p) => typeof p === 'string' && p.length > 0)
|
|
87
|
+
: [];
|
|
88
|
+
if (extra.length === 0) return result;
|
|
89
|
+
return {
|
|
90
|
+
...result,
|
|
91
|
+
patterns: [...defaults, ...extra],
|
|
92
|
+
source: 'defaults+config',
|
|
93
|
+
added: extra.length,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Does `relPath` (repo-root-relative, forward slashes) match any pattern?
|
|
98
|
+
export function isAllowedPath(relPath, patterns) {
|
|
99
|
+
for (const pat of patterns) {
|
|
100
|
+
if (matchesGlob(relPath, pat)) return true;
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Split `paths` by the allowlist. Preserves input order in each bucket.
|
|
106
|
+
export function classifyConflicts(paths, patterns) {
|
|
107
|
+
const allowed = [];
|
|
108
|
+
const disallowed = [];
|
|
109
|
+
for (const p of paths) {
|
|
110
|
+
if (isAllowedPath(p, patterns)) allowed.push(p);
|
|
111
|
+
else disallowed.push(p);
|
|
112
|
+
}
|
|
113
|
+
return { allowed, disallowed };
|
|
114
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
// estate-sync — registry-scoped commit set.
|
|
2
|
+
//
|
|
3
|
+
// Replaces the blind `git add -A` with a computed COMMIT SCOPE: when
|
|
4
|
+
// `<repoRoot>/operator/registry/{devices.json,repos.json}` exist, this
|
|
5
|
+
// module resolves the current device (same hostname short-name algorithm
|
|
6
|
+
// as `operator/extensions/tools/library-sync/run.mjs`'s resolveDeviceId),
|
|
7
|
+
// derives every mount `to` subtree for repos this device is entitled to,
|
|
8
|
+
// and returns that set as the EXCLUSION list. `.vault/**` is excluded
|
|
9
|
+
// unconditionally regardless of registry state.
|
|
10
|
+
//
|
|
11
|
+
// Per the library-sync-solidification architecture decision (D1/D2): the
|
|
12
|
+
// in-scope (committable) set is everything NOT under any mount `to` path.
|
|
13
|
+
// This is deliberately simpler than library-sync's own down-path owner-
|
|
14
|
+
// exclusion (which carves out THIS device's own upPath-owned mount) —
|
|
15
|
+
// estate-sync excludes ALL mount `to` subtrees unconditionally, including
|
|
16
|
+
// the device's own agent tree, because D2 moves `agents/*` run-state
|
|
17
|
+
// wholly onto the library pipeline; estate-sync (the git layer) must never
|
|
18
|
+
// be a second writer for any mount-owned path, owned-by-this-device or not.
|
|
19
|
+
//
|
|
20
|
+
// Degrade-gracefully-never-crash: any registry read/parse/resolve failure
|
|
21
|
+
// (missing files, malformed JSON, unknown device) falls back to
|
|
22
|
+
// `registryPresent: false` with a warning explaining why — the caller
|
|
23
|
+
// (run.mjs) then falls back to the pre-rewrite unscoped `git add -A`
|
|
24
|
+
// behaviour with a loud warning, never a silent unscoped commit and never
|
|
25
|
+
// a crashed sync run.
|
|
26
|
+
|
|
27
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
28
|
+
import { hostname as osHostname } from 'node:os';
|
|
29
|
+
import { join } from 'node:path';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resolve which device id to run as, matching os.hostname() against each
|
|
33
|
+
* device's `host` field (exact, then prefix-before-first-dot) or against
|
|
34
|
+
* the device id itself. Mirrors
|
|
35
|
+
* `operator/extensions/tools/library-sync/run.mjs`'s resolveDeviceId
|
|
36
|
+
* exactly, so the two tools never disagree about "which device am I".
|
|
37
|
+
*
|
|
38
|
+
* @param {object} devicesDoc
|
|
39
|
+
* @param {string} currentHostname
|
|
40
|
+
* @returns {string|null}
|
|
41
|
+
*/
|
|
42
|
+
export function resolveDeviceId(devicesDoc, currentHostname) {
|
|
43
|
+
const shortCurrent = currentHostname.split('.')[0];
|
|
44
|
+
const devices = (devicesDoc && devicesDoc.devices) || {};
|
|
45
|
+
for (const [id, device] of Object.entries(devices)) {
|
|
46
|
+
if (device.host === currentHostname) return id;
|
|
47
|
+
if (id === currentHostname || id === shortCurrent) return id;
|
|
48
|
+
}
|
|
49
|
+
for (const [id, device] of Object.entries(devices)) {
|
|
50
|
+
if (typeof device.host === 'string' && device.host.split('.')[0] === shortCurrent) return id;
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resolve the repos a device is entitled to, paired with their mount
|
|
57
|
+
* definitions. Never throws — an entitlement referencing a repo id absent
|
|
58
|
+
* from repos.json is skipped with a warning (registry drift, not a reason
|
|
59
|
+
* to crash a 30-minute cron).
|
|
60
|
+
*
|
|
61
|
+
* @param {object} registry - { devices, repos } parsed JSON docs
|
|
62
|
+
* @param {string} deviceId
|
|
63
|
+
* @returns {{ entitled: { repoId: string, repo: object }[], warnings: string[] }}
|
|
64
|
+
*/
|
|
65
|
+
export function resolveEntitlements(registry, deviceId) {
|
|
66
|
+
const warnings = [];
|
|
67
|
+
const device = registry.devices?.devices?.[deviceId];
|
|
68
|
+
if (!device) {
|
|
69
|
+
warnings.push(`device "${deviceId}" not found in devices.json`);
|
|
70
|
+
return { entitled: [], warnings };
|
|
71
|
+
}
|
|
72
|
+
const repoIds = device.entitlements?.repos;
|
|
73
|
+
if (!Array.isArray(repoIds)) {
|
|
74
|
+
warnings.push(`device "${deviceId}" has no entitlements.repos array`);
|
|
75
|
+
return { entitled: [], warnings };
|
|
76
|
+
}
|
|
77
|
+
const entitled = [];
|
|
78
|
+
for (const repoId of repoIds) {
|
|
79
|
+
const repo = registry.repos?.repos?.[repoId];
|
|
80
|
+
if (!repo) {
|
|
81
|
+
warnings.push(`device "${deviceId}" entitles repo "${repoId}" not present in repos.json`);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
entitled.push({ repoId, repo });
|
|
85
|
+
}
|
|
86
|
+
return { entitled, warnings };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Normalise a mount `to` path to a POSIX-style directory prefix ending in "/". */
|
|
90
|
+
function normalizeDir(to) {
|
|
91
|
+
let p = String(to).split('\\').join('/');
|
|
92
|
+
if (p.startsWith('./')) p = p.slice(2);
|
|
93
|
+
if (!p.endsWith('/')) p += '/';
|
|
94
|
+
return p;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Derive the exclusion set: every entitled repo's mount `to` subtree,
|
|
99
|
+
* unconditionally (D1/D2 — no ownership carve-out; see module doc), plus
|
|
100
|
+
* `.vault/**` unconditionally. Sorted, deduped.
|
|
101
|
+
*
|
|
102
|
+
* @param {{ repoId: string, repo: object }[]} entitled
|
|
103
|
+
* @returns {string[]} POSIX-relative directory prefixes, each ending in "/"
|
|
104
|
+
*/
|
|
105
|
+
export function computeExcludeDirs(entitled) {
|
|
106
|
+
const dirs = new Set();
|
|
107
|
+
for (const { repo } of entitled) {
|
|
108
|
+
const mounts = Array.isArray(repo.mounts) ? repo.mounts : [];
|
|
109
|
+
for (const mount of mounts) {
|
|
110
|
+
if (mount && typeof mount.to === 'string' && mount.to.length > 0) {
|
|
111
|
+
dirs.add(normalizeDir(mount.to));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
dirs.add('.vault/');
|
|
116
|
+
return [...dirs].sort();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function readJson(path) {
|
|
120
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Compute the full commit-scope decision for this device/repo, degrading
|
|
125
|
+
* gracefully at every failure point (see module doc).
|
|
126
|
+
*
|
|
127
|
+
* @param {object} opts
|
|
128
|
+
* @param {string} opts.repoRoot
|
|
129
|
+
* @param {() => string} [opts.hostnameFn] - injectable for tests
|
|
130
|
+
* @returns {{
|
|
131
|
+
* registryPresent: boolean,
|
|
132
|
+
* deviceId?: string,
|
|
133
|
+
* excludeDirs: string[],
|
|
134
|
+
* warnings: string[],
|
|
135
|
+
* }}
|
|
136
|
+
*/
|
|
137
|
+
export function computeCommitScope({ repoRoot, hostnameFn = osHostname }) {
|
|
138
|
+
const devicesPath = join(repoRoot, 'operator/registry/devices.json');
|
|
139
|
+
const reposPath = join(repoRoot, 'operator/registry/repos.json');
|
|
140
|
+
|
|
141
|
+
if (!existsSync(devicesPath) || !existsSync(reposPath)) {
|
|
142
|
+
return { registryPresent: false, excludeDirs: [], warnings: [] };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let devicesDoc;
|
|
146
|
+
let reposDoc;
|
|
147
|
+
try {
|
|
148
|
+
devicesDoc = readJson(devicesPath);
|
|
149
|
+
reposDoc = readJson(reposPath);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
return {
|
|
152
|
+
registryPresent: false,
|
|
153
|
+
excludeDirs: [],
|
|
154
|
+
warnings: [`registry present but unreadable (${err.message}) — falling back to unscoped`],
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const deviceId = resolveDeviceId(devicesDoc, hostnameFn());
|
|
159
|
+
if (!deviceId) {
|
|
160
|
+
return {
|
|
161
|
+
registryPresent: false,
|
|
162
|
+
excludeDirs: [],
|
|
163
|
+
warnings: [`could not resolve a device id from hostname "${hostnameFn()}" against ${devicesPath} — falling back to unscoped`],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const { entitled, warnings } = resolveEntitlements({ devices: devicesDoc, repos: reposDoc }, deviceId);
|
|
168
|
+
const excludeDirs = computeExcludeDirs(entitled);
|
|
169
|
+
|
|
170
|
+
return { registryPresent: true, deviceId, excludeDirs, warnings };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Parse `git status --porcelain` output into a flat list of paths. Rename
|
|
175
|
+
* entries (`R old -> new`) contribute BOTH paths — `git add -- <path>`
|
|
176
|
+
* correctly stages a deletion when given an explicit path that no longer
|
|
177
|
+
* exists on disk (git >= 2.0), so staging both sides of a rename is
|
|
178
|
+
* sufficient without needing `-A` semantics.
|
|
179
|
+
*
|
|
180
|
+
* Shares the same C-style-quoting limitation as `listConflictedPaths` in
|
|
181
|
+
* git.mjs (paths with unusual characters are not unquoted) — acceptable,
|
|
182
|
+
* matches existing precedent in this module.
|
|
183
|
+
*
|
|
184
|
+
* @param {string} porcelainOutput
|
|
185
|
+
* @returns {string[]}
|
|
186
|
+
*/
|
|
187
|
+
export function parsePorcelainPaths(porcelainOutput) {
|
|
188
|
+
const out = [];
|
|
189
|
+
for (const rawLine of porcelainOutput.split('\n')) {
|
|
190
|
+
if (rawLine.length < 4) continue;
|
|
191
|
+
const rest = rawLine.slice(3);
|
|
192
|
+
const arrowIdx = rest.indexOf(' -> ');
|
|
193
|
+
if (arrowIdx !== -1) {
|
|
194
|
+
out.push(rest.slice(0, arrowIdx));
|
|
195
|
+
out.push(rest.slice(arrowIdx + 4));
|
|
196
|
+
} else {
|
|
197
|
+
out.push(rest);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Is `path` under one of `excludeDirs` (each ending in "/")? */
|
|
204
|
+
export function isExcluded(path, excludeDirs) {
|
|
205
|
+
return excludeDirs.some((dir) => path === dir.slice(0, -1) || path.startsWith(dir));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Filter porcelain output down to the in-scope (committable) paths —
|
|
210
|
+
* everything NOT under an excluded subtree. Dedupes (a path can appear
|
|
211
|
+
* twice from a rename pair sharing a prefix) while preserving first-seen
|
|
212
|
+
* order.
|
|
213
|
+
*
|
|
214
|
+
* @param {string} porcelainOutput
|
|
215
|
+
* @param {string[]} excludeDirs
|
|
216
|
+
* @returns {{ inScope: string[], excludedCount: number }}
|
|
217
|
+
*/
|
|
218
|
+
export function filterInScopePaths(porcelainOutput, excludeDirs) {
|
|
219
|
+
const allPaths = parsePorcelainPaths(porcelainOutput);
|
|
220
|
+
const seen = new Set();
|
|
221
|
+
const inScope = [];
|
|
222
|
+
let excludedCount = 0;
|
|
223
|
+
for (const p of allPaths) {
|
|
224
|
+
if (isExcluded(p, excludeDirs)) {
|
|
225
|
+
excludedCount += 1;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (!seen.has(p)) {
|
|
229
|
+
seen.add(p);
|
|
230
|
+
inScope.push(p);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return { inScope, excludedCount };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Render the scope-decision section for the commit body (requirement:
|
|
238
|
+
* "report the scope decision in the commit body — counts: staged N paths,
|
|
239
|
+
* excluded M subtrees by rule").
|
|
240
|
+
*
|
|
241
|
+
* @param {object} scope - result of computeCommitScope
|
|
242
|
+
* @param {object} counts
|
|
243
|
+
* @param {number} counts.staged
|
|
244
|
+
* @param {number} counts.excludedPaths - dirty paths that were excluded
|
|
245
|
+
* @returns {string}
|
|
246
|
+
*/
|
|
247
|
+
export function renderScopeSection(scope, { staged, excludedPaths }) {
|
|
248
|
+
if (!scope.registryPresent) {
|
|
249
|
+
return 'estate-sync: no registry found, unscoped commit';
|
|
250
|
+
}
|
|
251
|
+
const lines = [
|
|
252
|
+
`estate-sync scope: registry-scoped (device=${scope.deviceId})`,
|
|
253
|
+
` staged: ${staged} path(s)`,
|
|
254
|
+
` excluded: ${scope.excludeDirs.length} subtree(s) by rule (${excludedPaths} dirty path(s) matched) — ${scope.excludeDirs.join(', ')}`,
|
|
255
|
+
];
|
|
256
|
+
if (scope.warnings.length > 0) {
|
|
257
|
+
lines.push(` warnings: ${scope.warnings.join('; ')}`);
|
|
258
|
+
}
|
|
259
|
+
return lines.join('\n');
|
|
260
|
+
}
|