@moifort/stack 0.0.0-stage → 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/CHANGELOG.md +18 -0
- package/README.md +26 -2
- package/bin/stack.ts +109 -0
- package/hooks/shutdown-simulators-after-push.sh +19 -0
- package/package.json +40 -4
- package/routines/daily-health.md +78 -0
- package/routines/deprecations.md +23 -0
- package/routines/sentry-autofix.md +76 -0
- package/routines/stack-upgrade.md +45 -0
- package/rules/00-collaboration.md +81 -0
- package/rules/01-workflow.md +81 -0
- package/rules/02-commits.md +71 -0
- package/rules/03-code-review.md +25 -0
- package/rules/04-push.md +75 -0
- package/rules/05-release.md +40 -0
- package/rules/06-changelog.md +48 -0
- package/rules/07-appstore.md +77 -0
- package/rules/08-copy.md +52 -0
- package/rules/09-backend.md +132 -0
- package/rules/10-ios.md +206 -0
- package/rules/11-ops.md +114 -0
- package/rules/12-ci.md +32 -0
- package/rules/13-ai.md +71 -0
- package/rules/14-infra.md +90 -0
- package/rules/15-tooling.md +81 -0
- package/rules/16-routines.md +69 -0
- package/rules/17-landing.md +76 -0
- package/skills/appstore/SKILL.md +53 -0
- package/skills/domain/SKILL.md +136 -0
- package/skills/graphql/SKILL.md +83 -0
- package/skills/ios-feature/SKILL.md +94 -0
- package/skills/stack-install/SKILL.md +35 -0
- package/skills/stack-upgrade/SKILL.md +27 -0
- package/src/catalogue.ts +33 -0
- package/src/config.ts +20 -0
- package/src/init.ts +63 -0
- package/src/migrations.ts +72 -0
- package/src/migrations.unit.test.ts +44 -0
- package/src/package.ts +8 -0
- package/src/render.ts +13 -0
- package/src/render.unit.test.ts +17 -0
- package/src/sync.int.test.ts +103 -0
- package/src/sync.ts +161 -0
- package/src/templates.ts +121 -0
- package/src/validate-content.ts +65 -0
- package/templates/backend/.env.example +10 -0
- package/templates/backend/.github/workflows/deploy.yml +151 -0
- package/templates/backend/.github/workflows/schema.yml +59 -0
- package/templates/backend/.github/workflows/test-feat.yml +40 -0
- package/templates/backend/.github/workflows/test-int.yml +40 -0
- package/templates/backend/.github/workflows/test-unit.yml +64 -0
- package/templates/backend/docs/api-evolution.md +67 -0
- package/templates/backend/firebase.json +21 -0
- package/templates/backend/firestore.indexes.json +4 -0
- package/templates/backend/firestore.rules +11 -0
- package/templates/backend/infra/auth.tf +54 -0
- package/templates/backend/infra/backend.tf +6 -0
- package/templates/backend/infra/cicd.tf +63 -0
- package/templates/backend/infra/firebase.tf +26 -0
- package/templates/backend/infra/firestore.tf +53 -0
- package/templates/backend/infra/function.tf +171 -0
- package/templates/backend/infra/main.tf +26 -0
- package/templates/backend/infra/observer.tf +18 -0
- package/templates/backend/infra/outputs.tf +30 -0
- package/templates/backend/infra/project.tf +64 -0
- package/templates/backend/infra/scheduler.tf +28 -0
- package/templates/backend/infra/secrets.tf +44 -0
- package/templates/backend/infra/state.tf +13 -0
- package/templates/backend/infra/terraform.tfvars.example +16 -0
- package/templates/backend/infra/variables.tf +104 -0
- package/templates/backend/infra/versions.tf +30 -0
- package/templates/backend/nitro.config.ts +40 -0
- package/templates/backend/package.json +51 -0
- package/templates/backend/scripts/deprecations/inspector-rule.ts +29 -0
- package/templates/backend/scripts/deprecations/status.ts +50 -0
- package/templates/backend/scripts/deprecations/status.unit.test.ts +57 -0
- package/templates/backend/scripts/deprecations/usage.ts +79 -0
- package/templates/backend/scripts/deprecations/usage.unit.test.ts +64 -0
- package/templates/backend/scripts/deprecations.ts +224 -0
- package/templates/backend/scripts/export-graphql-schema.ts +11 -0
- package/templates/backend/scripts/generate-changelog-asset.ts +27 -0
- package/templates/backend/scripts/install-terraform.sh +61 -0
- package/templates/backend/scripts/tf +11 -0
- package/templates/backend/server/domain/changelog/business-rules.ts +60 -0
- package/templates/backend/server/domain/changelog/business-rules.unit.test.ts +88 -0
- package/templates/backend/server/domain/changelog/infrastructure/graphql/queries.ts +17 -0
- package/templates/backend/server/domain/changelog/infrastructure/graphql/types.ts +23 -0
- package/templates/backend/server/domain/changelog/primitives.ts +19 -0
- package/templates/backend/server/domain/changelog/query.ts +13 -0
- package/templates/backend/server/domain/changelog/query.unit.test.ts +19 -0
- package/templates/backend/server/domain/changelog/types.ts +9 -0
- package/templates/backend/server/domain/shared/graphql/builder.ts +52 -0
- package/templates/backend/server/domain/shared/graphql/errors.ts +19 -0
- package/templates/backend/server/domain/shared/graphql/scalars.ts +51 -0
- package/templates/backend/server/domain/shared/graphql/schema.ts +21 -0
- package/templates/backend/server/domain/shared/language.ts +26 -0
- package/templates/backend/server/domain/shared/page.ts +25 -0
- package/templates/backend/server/domain/shared/page.unit.test.ts +51 -0
- package/templates/backend/server/domain/shared/primitives.ts +50 -0
- package/templates/backend/server/domain/shared/primitives.unit.test.ts +25 -0
- package/templates/backend/server/domain/shared/types.ts +14 -0
- package/templates/backend/server/domain/user/command.ts +16 -0
- package/templates/backend/server/domain/user/infrastructure/graphql/inputs.ts +12 -0
- package/templates/backend/server/domain/user/infrastructure/graphql/mutations.ts +40 -0
- package/templates/backend/server/domain/user/infrastructure/graphql/queries.ts +13 -0
- package/templates/backend/server/domain/user/infrastructure/graphql/types.ts +35 -0
- package/templates/backend/server/domain/user/infrastructure/repository.ts +59 -0
- package/templates/backend/server/domain/user/mutations.feat.test.ts +42 -0
- package/templates/backend/server/domain/user/query.int.test.ts +49 -0
- package/templates/backend/server/domain/user/query.ts +34 -0
- package/templates/backend/server/domain/user/types.ts +20 -0
- package/templates/backend/server/domain/user/use-case.ts +18 -0
- package/templates/backend/server/middleware/auth.ts +54 -0
- package/templates/backend/server/plugins/00-sentry.ts +74 -0
- package/templates/backend/server/plugins/01-request-log.ts +28 -0
- package/templates/backend/server/plugins/02-graphql.ts +69 -0
- package/templates/backend/server/plugins/03-boot-time.ts +21 -0
- package/templates/backend/server/routes/admin/migrate.post.ts +9 -0
- package/templates/backend/server/routes/app-config.get.ts +15 -0
- package/templates/backend/server/routes/graphql.ts +52 -0
- package/templates/backend/server/system/app-support.ts +17 -0
- package/templates/backend/server/system/config/index.ts +18 -0
- package/templates/backend/server/system/config/primitives.ts +20 -0
- package/templates/backend/server/system/config/primitives.unit.test.ts +13 -0
- package/templates/backend/server/system/config/types.ts +8 -0
- package/templates/backend/server/system/firebase.ts +19 -0
- package/templates/backend/server/system/identity.ts +18 -0
- package/templates/backend/server/system/logger.ts +119 -0
- package/templates/backend/server/system/logger.unit.test.ts +109 -0
- package/templates/backend/server/system/migration/migrations/index.ts +6 -0
- package/templates/backend/server/system/migration/primitives.ts +18 -0
- package/templates/backend/server/system/migration/runner.ts +52 -0
- package/templates/backend/server/system/migration/types.ts +16 -0
- package/templates/backend/server/system/read-meter.ts +100 -0
- package/templates/backend/server/system/read-meter.unit.test.ts +97 -0
- package/templates/backend/server/system/request-cache.ts +139 -0
- package/templates/backend/server/system/request-cache.unit.test.ts +166 -0
- package/templates/backend/server/system/secret-box.ts +49 -0
- package/templates/backend/server/system/secret-box.unit.test.ts +46 -0
- package/templates/backend/server/test/bdd.ts +12 -0
- package/templates/backend/server/test/fake-firestore.ts +483 -0
- package/templates/backend/server/utils/apollo.ts +13 -0
- package/templates/backend/server/utils/fetch.ts +5 -0
- package/templates/backend/server/utils/firestore.ts +91 -0
- package/templates/backend/server/utils/input.ts +36 -0
- package/templates/backend/server/utils/input.unit.test.ts +43 -0
- package/templates/backend/server/utils/slug.ts +21 -0
- package/templates/backend/tsconfig.json +16 -0
- package/templates/base/.github/workflows/renovate.yml +17 -0
- package/templates/base/CHANGELOG.fr.md +3 -0
- package/templates/base/CHANGELOG.md +7 -0
- package/templates/base/CLAUDE.md +34 -0
- package/templates/base/README.md +12 -0
- package/templates/base/biome.json +47 -0
- package/templates/base/bunfig.toml +2 -0
- package/templates/base/docs/adr/0001-built-on-the-polyforms-stack.md +21 -0
- package/templates/base/docs/data-recovery.md +116 -0
- package/templates/base/docs/ideas.md +4 -0
- package/templates/base/project-context.md +43 -0
- package/templates/base/renovate.json +37 -0
- package/templates/ios/.github/workflows/build-ios.yml +168 -0
- package/templates/ios/.github/workflows/ios-upload.yml +308 -0
- package/templates/ios/.github/workflows/release-ios.yml +178 -0
- package/templates/ios/.github/workflows/testflight-ios.yml +57 -0
- package/templates/ios/infra/Gemfile +5 -0
- package/templates/ios/infra/fastlane/Appfile +1 -0
- package/templates/ios/infra/fastlane/Fastfile +54 -0
- package/templates/ios/infra/fastlane/metadata/copyright.txt +1 -0
- package/templates/ios/infra/fastlane/metadata/en-US/description.txt +0 -0
- package/templates/ios/infra/fastlane/metadata/en-US/keywords.txt +0 -0
- package/templates/ios/infra/fastlane/metadata/en-US/name.txt +1 -0
- package/templates/ios/infra/fastlane/metadata/en-US/privacy_url.txt +1 -0
- package/templates/ios/infra/fastlane/metadata/en-US/promotional_text.txt +0 -0
- package/templates/ios/infra/fastlane/metadata/en-US/subtitle.txt +1 -0
- package/templates/ios/infra/fastlane/metadata/en-US/support_url.txt +1 -0
- package/templates/ios/infra/fastlane/metadata/fr-FR/description.txt +0 -0
- package/templates/ios/infra/fastlane/metadata/fr-FR/keywords.txt +0 -0
- package/templates/ios/infra/fastlane/metadata/fr-FR/name.txt +1 -0
- package/templates/ios/infra/fastlane/metadata/fr-FR/privacy_url.txt +1 -0
- package/templates/ios/infra/fastlane/metadata/fr-FR/promotional_text.txt +0 -0
- package/templates/ios/infra/fastlane/metadata/fr-FR/subtitle.txt +1 -0
- package/templates/ios/infra/fastlane/metadata/fr-FR/support_url.txt +1 -0
- package/templates/ios/infra/fastlane/metadata/primary_category.txt +1 -0
- package/templates/ios/infra/ios.tf +28 -0
- package/templates/ios/ios/ExportOptions.plist +26 -0
- package/templates/ios/ios/apollo-codegen-config.json +16 -0
- package/templates/ios/ios/project.yml +94 -0
- package/templates/ios/ios/{{APP_NAME}}/App.swift +46 -0
- package/templates/ios/ios/{{APP_NAME}}/Assets.xcassets/AppIcon.appiconset/AppIcon.png +0 -0
- package/templates/ios/ios/{{APP_NAME}}/Assets.xcassets/AppIcon.appiconset/Contents.json +14 -0
- package/templates/ios/ios/{{APP_NAME}}/Assets.xcassets/Contents.json +6 -0
- package/templates/ios/ios/{{APP_NAME}}/Assets.xcassets/LaunchBackground.colorset/Contents.json +38 -0
- package/templates/ios/ios/{{APP_NAME}}/ContentView.swift +17 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Auth/AppleNonce.swift +33 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Auth/AuthRoot.swift +74 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Auth/AuthSession.swift +86 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Auth/LoginScreen.swift +83 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Home/HomeScreen.swift +21 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Home/components/pages/HomePage.swift +40 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Onboarding/AccountEnvironment.swift +7 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Onboarding/GraphQL/Onboarding.graphql +16 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Onboarding/OnboardingAPI.swift +28 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Onboarding/OnboardingClient.swift +26 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Onboarding/OnboardingGate.swift +50 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Onboarding/OnboardingScreen.swift +69 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Onboarding/components/pages/FirstNamePage.swift +66 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Onboarding/components/pages/WelcomePage.swift +37 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Settings/ChangelogScreen.swift +25 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Settings/ChangelogViewModel.swift +27 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Settings/GraphQL/Settings.graphql +11 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Settings/ProfileSettingsScreen.swift +30 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Settings/ProfileSettingsViewModel.swift +51 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Settings/SettingsAPI.swift +37 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Settings/SettingsClient.swift +40 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Settings/SettingsHomeScreen.swift +39 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Settings/components/organisms/DeleteAccountButton.swift +30 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Settings/components/organisms/SettingsRow.swift +56 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Settings/components/organisms/SignOutButton.swift +23 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Settings/components/pages/ChangelogPage.swift +59 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Settings/components/pages/FeedbackSheet.swift +118 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Settings/components/pages/ProfileSettingsPage.swift +90 -0
- package/templates/ios/ios/{{APP_NAME}}/Features/Settings/components/pages/SettingsHomePage.swift +70 -0
- package/templates/ios/ios/{{APP_NAME}}/Info.plist +14 -0
- package/templates/ios/ios/{{APP_NAME}}/Localizable.xcstrings +1030 -0
- package/templates/ios/ios/{{APP_NAME}}/PrivacyInfo.xcprivacy +122 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/APIClient.swift +23 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/AcceptLanguageInterceptor.swift +20 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/Components/atoms/AsyncButton.swift +73 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/Components/atoms/AsyncToolbarButton.swift +69 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/Components/atoms/ToolbarIconButton.swift +46 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/Components/molecules/EmptyState.swift +132 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/Components/molecules/LabeledInfoRow.swift +29 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/Components/styles/CaptionLabelStyle.swift +45 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/Components/styles/Copyable.swift +32 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/Components/styles/EdgeToEdgeSeparator.swift +14 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/Components/styles/RowLabelStyle.swift +58 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/CursorPage.swift +26 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/DataChanges.swift +19 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/ErrorPresenter.swift +62 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/ErrorReporting.swift +35 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/FeedbackReporting.swift +42 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/FirebaseTokenInterceptor.swift +49 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/GraphQLClient.swift +54 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/GraphQLHelpers.swift +128 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/GraphQLLoggingInterceptor.swift +28 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/LoadGate.swift +30 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/Secrets.swift.example +9 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/Services/AppSupportGate.swift +50 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/Shell/StartupLoadingView.swift +23 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/Shell/UpdateRequiredView.swift +24 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/Showcase.swift +13 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/SnapshotCache.swift +78 -0
- package/templates/ios/ios/{{APP_NAME}}/Shared/UITestEnvironment.swift +70 -0
- package/templates/ios/ios/{{APP_NAME}}/{{APP_NAME}}.entitlements +10 -0
- package/templates/ios/ios/{{APP_NAME}}Tests/Onboarding/OnboardingGateTests.swift +38 -0
- package/templates/ios/ios/{{APP_NAME}}Tests/Settings/SettingsViewModelTests.swift +63 -0
- package/templates/ios/ios/{{APP_NAME}}Tests/Support/TestDoubles.swift +35 -0
- package/templates/ios/package.merge.json +10 -0
- package/templates/ios/scripts/apollo-codegen.ts +31 -0
- package/templates/ios/scripts/check-ios-components.ts +72 -0
- package/templates/ios/scripts/check-strings.ts +103 -0
- package/templates/ios/scripts/install-device.sh +29 -0
- package/templates/ios/scripts/testflight.ts +213 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Every version lists what changed and, when a rule change makes existing code non compliant,
|
|
4
|
+
its migrations with their risk. The upgrade routine reads this first.
|
|
5
|
+
|
|
6
|
+
## 0.1.0 (2026.10.21)
|
|
7
|
+
|
|
8
|
+
First version, harvested from Shiori.
|
|
9
|
+
|
|
10
|
+
- Rules `00` to `17`: collaboration, workflow, commits, review, push, release, changelog,
|
|
11
|
+
App Store, copy, backend, iOS, operations, CI, AI, infrastructure, tooling, routines,
|
|
12
|
+
landing page.
|
|
13
|
+
- Routines: Sentry autofix, daily health, upgrades, deprecations.
|
|
14
|
+
- Skills: domain, graphql, ios-feature, appstore, stack-install, stack-upgrade.
|
|
15
|
+
- Templates: base, backend (Nitro, Firestore, GraphQL, Terraform, workflows), ios (SwiftUI
|
|
16
|
+
app generated by XcodeGen, workflows, fastlane).
|
|
17
|
+
- CLI: `init`, `sync`, `check`, `migrations`, `placeholders`.
|
|
18
|
+
- Migrations: none.
|
package/README.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @moifort/stack
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
How Polyforms apps are built, as one versioned package: the rules Claude follows on every
|
|
4
|
+
project, the skills and routines it runs, and the templates a new app starts from. A Firebase
|
|
5
|
+
backend in TypeScript, a SwiftUI app, Terraform, GitHub Actions, Sentry.
|
|
6
|
+
|
|
7
|
+
## Start an app
|
|
8
|
+
|
|
9
|
+
1. `mkdir <app> && cd <app> && git init`
|
|
10
|
+
2. `bunx @moifort/stack init --stacks backend,ios`
|
|
11
|
+
3. `bun install && bun run preflight`
|
|
12
|
+
|
|
13
|
+
## Keep an app up to date
|
|
14
|
+
|
|
15
|
+
Renovate opens a pull request when a new version is published. The upgrade routine syncs the
|
|
16
|
+
rules into the project's `stack/` folder and applies the version's migrations, listed in
|
|
17
|
+
[CHANGELOG.md](CHANGELOG.md).
|
|
18
|
+
|
|
19
|
+
## What is inside
|
|
20
|
+
|
|
21
|
+
| Folder | What |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `rules/` | The rules, imported by each project's `CLAUDE.md` through `stack/rules/` |
|
|
24
|
+
| `skills/` | The long forms, copied into `.claude/skills/` |
|
|
25
|
+
| `routines/` | The scheduled Claude tasks |
|
|
26
|
+
| `templates/` | What `init` writes into a new project, per stack |
|
|
27
|
+
| `migrations/` | What to change in a project when a version changes a rule |
|
package/bin/stack.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { parseArgs } from 'node:util'
|
|
3
|
+
import { readConfig, STACKS, type Stack } from '~/config'
|
|
4
|
+
import { defaultPlaceholders, requiredPlaceholders, writeConfig } from '~/init'
|
|
5
|
+
import { migrationsBetween, readMigrations } from '~/migrations'
|
|
6
|
+
import { packageDir, packageVersion } from '~/package'
|
|
7
|
+
import { applyPlan, applyTemplates, checkPlan, planSync } from '~/sync'
|
|
8
|
+
|
|
9
|
+
const usage = `stack <command>
|
|
10
|
+
|
|
11
|
+
init --stacks backend,ios --set APP_NAME=Name --set MAIN_BRANCH=main write stack.config.json, then sync
|
|
12
|
+
sync render rules, routines, skills, hooks into the project
|
|
13
|
+
check non-zero when the project differs from sync
|
|
14
|
+
migrations <from> <to> [--json] the migrations between two versions, for the project's stacks
|
|
15
|
+
placeholders --stacks backend,ios the placeholders those stacks need
|
|
16
|
+
`
|
|
17
|
+
|
|
18
|
+
const main = async (argv: string[]): Promise<number> => {
|
|
19
|
+
const [command, ...rest] = argv
|
|
20
|
+
const projectDir = process.cwd()
|
|
21
|
+
const pkg = packageDir()
|
|
22
|
+
const version = await packageVersion()
|
|
23
|
+
|
|
24
|
+
switch (command) {
|
|
25
|
+
case 'init': {
|
|
26
|
+
const { values } = parseArgs({
|
|
27
|
+
args: rest,
|
|
28
|
+
options: { stacks: { type: 'string' }, set: { type: 'string', multiple: true } },
|
|
29
|
+
})
|
|
30
|
+
const stacks = (values.stacks ?? '').split(',').filter(Boolean) as Stack[]
|
|
31
|
+
for (const s of stacks)
|
|
32
|
+
if (!STACKS.includes(s)) throw new Error(`unknown stack ${s}; known: ${STACKS.join(', ')}`)
|
|
33
|
+
const placeholders: Record<string, string> = defaultPlaceholders(projectDir)
|
|
34
|
+
for (const pair of values.set ?? []) {
|
|
35
|
+
const i = pair.indexOf('=')
|
|
36
|
+
if (i <= 0) throw new Error(`--set expects NAME=value, got ${pair}`)
|
|
37
|
+
placeholders[pair.slice(0, i)] = pair.slice(i + 1)
|
|
38
|
+
}
|
|
39
|
+
const required = await requiredPlaceholders(pkg, stacks)
|
|
40
|
+
const missing = required.filter((n) => placeholders[n] === undefined)
|
|
41
|
+
if (missing.length > 0) {
|
|
42
|
+
console.error(
|
|
43
|
+
`missing placeholders: ${missing.join(', ')}\n(pass each with --set NAME=value)`,
|
|
44
|
+
)
|
|
45
|
+
return 1
|
|
46
|
+
}
|
|
47
|
+
const used = Object.fromEntries(required.map((n) => [n, placeholders[n] as string]))
|
|
48
|
+
await writeConfig(projectDir, { stacks, placeholders: used, exclude: [] })
|
|
49
|
+
const config = await readConfig(projectDir)
|
|
50
|
+
const today = new Date().toISOString().slice(0, 10)
|
|
51
|
+
const { written, kept } = await applyTemplates(pkg, projectDir, config, {
|
|
52
|
+
...config.placeholders,
|
|
53
|
+
STACK_VERSION: version,
|
|
54
|
+
TODAY: today,
|
|
55
|
+
})
|
|
56
|
+
const plan = await planSync(pkg, config, version)
|
|
57
|
+
await applyPlan(projectDir, plan)
|
|
58
|
+
console.log(
|
|
59
|
+
`initialised with stacks ${stacks.join(', ') || '(none)'}: ${written.length} files written, ${kept.length} kept, synced to ${version}`,
|
|
60
|
+
)
|
|
61
|
+
return 0
|
|
62
|
+
}
|
|
63
|
+
case 'sync': {
|
|
64
|
+
const plan = await planSync(pkg, await readConfig(projectDir), version)
|
|
65
|
+
await applyPlan(projectDir, plan)
|
|
66
|
+
console.log(`synced ${plan.files.size} files to ${version}`)
|
|
67
|
+
return 0
|
|
68
|
+
}
|
|
69
|
+
case 'check': {
|
|
70
|
+
const plan = await planSync(pkg, await readConfig(projectDir), version)
|
|
71
|
+
const problems = await checkPlan(projectDir, plan)
|
|
72
|
+
for (const p of problems) console.error(p)
|
|
73
|
+
if (problems.length === 0) console.log(`in sync with ${version}`)
|
|
74
|
+
return problems.length === 0 ? 0 : 1
|
|
75
|
+
}
|
|
76
|
+
case 'migrations': {
|
|
77
|
+
const { positionals, values } = parseArgs({
|
|
78
|
+
args: rest,
|
|
79
|
+
options: { json: { type: 'boolean' } },
|
|
80
|
+
allowPositionals: true,
|
|
81
|
+
})
|
|
82
|
+
const [from, to] = positionals
|
|
83
|
+
if (!from || !to) throw new Error('usage: migrations <from> <to>')
|
|
84
|
+
const config = await readConfig(projectDir)
|
|
85
|
+
const list = migrationsBetween(await readMigrations(pkg), from, to, config.stacks)
|
|
86
|
+
if (values.json)
|
|
87
|
+
console.log(
|
|
88
|
+
JSON.stringify(
|
|
89
|
+
list.map(({ body, ...m }) => m),
|
|
90
|
+
null,
|
|
91
|
+
2,
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
else for (const m of list) console.log(`${m.version} ${m.risk.padEnd(6)} ${m.id}`)
|
|
95
|
+
return 0
|
|
96
|
+
}
|
|
97
|
+
case 'placeholders': {
|
|
98
|
+
const { values } = parseArgs({ args: rest, options: { stacks: { type: 'string' } } })
|
|
99
|
+
const stacks = (values.stacks ?? '').split(',').filter(Boolean) as Stack[]
|
|
100
|
+
for (const name of await requiredPlaceholders(pkg, stacks)) console.log(name)
|
|
101
|
+
return 0
|
|
102
|
+
}
|
|
103
|
+
default:
|
|
104
|
+
console.log(usage)
|
|
105
|
+
return command === undefined || command === 'help' ? 0 : 1
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
process.exit(await main(process.argv.slice(2)))
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Hook PostToolUse (Bash) : après un `git push` réussi, éteint tous les
|
|
3
|
+
# simulateurs et quitte Simulator.app, qui sinon restent ouverts et occupent
|
|
4
|
+
# 50 à 70 % du processeur.
|
|
5
|
+
|
|
6
|
+
command=$(/usr/bin/python3 -c 'import json, sys; print(json.load(sys.stdin).get("tool_input", {}).get("command", ""))' 2>/dev/null)
|
|
7
|
+
|
|
8
|
+
printf '%s' "$command" | grep -Eq '(^|[;&|[:space:]])git([[:space:]]+-C[[:space:]]+[^[:space:]]+)?[[:space:]]+push([[:space:]]|$)' || exit 0
|
|
9
|
+
|
|
10
|
+
booted=$(xcrun simctl list devices booted 2>/dev/null | grep -c '(Booted)')
|
|
11
|
+
simulator_app=$(pgrep -x Simulator)
|
|
12
|
+
[ "$booted" -eq 0 ] && [ -z "$simulator_app" ] && exit 0
|
|
13
|
+
|
|
14
|
+
xcrun simctl shutdown all >/dev/null 2>&1
|
|
15
|
+
[ -n "$simulator_app" ] && osascript -e 'quit app "Simulator"' >/dev/null 2>&1
|
|
16
|
+
|
|
17
|
+
cat <<EOF
|
|
18
|
+
{"hookSpecificOutput": {"hookEventName": "PostToolUse", "additionalContext": "Push terminé : $booted simulateur(s) éteint(s) et Simulator.app fermé automatiquement (hook shutdown-simulators-after-push)."}}
|
|
19
|
+
EOF
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moifort/stack",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The Polyforms stack: rules, skills, hooks, routines and templates for a Firebase + iOS app, versioned and upgraded through Renovate.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": "github:moifort/polyforms-stack",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"stack": "bin/stack.ts"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin",
|
|
13
|
+
"src",
|
|
14
|
+
"rules",
|
|
15
|
+
"skills",
|
|
16
|
+
"hooks",
|
|
17
|
+
"routines",
|
|
18
|
+
"templates",
|
|
19
|
+
"migrations",
|
|
20
|
+
"CHANGELOG.md"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"test": "bun test",
|
|
24
|
+
"test:unit": "bun test .unit.test",
|
|
25
|
+
"test:int": "bun test .int.test",
|
|
26
|
+
"typecheck": "bunx tsc --noEmit",
|
|
27
|
+
"lint": "bunx biome check",
|
|
28
|
+
"lint:fix": "bunx biome check --write",
|
|
29
|
+
"validate": "bun src/validate-content.ts",
|
|
30
|
+
"preflight": "bun run typecheck && bun run lint && bun run test:unit && bun run test:int && bun run validate"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"semver": "7.7.2",
|
|
34
|
+
"zod": "4.1.12"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@biomejs/biome": "2.5.15",
|
|
38
|
+
"@types/bun": "1.4.3",
|
|
39
|
+
"@types/semver": "7.7.1",
|
|
40
|
+
"typescript": "5.9.3"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: daily-health
|
|
3
|
+
scope: project
|
|
4
|
+
runtime: cloud
|
|
5
|
+
cadence: "0 12 * * *"
|
|
6
|
+
title: "{{APP_NAME}} health (daily at noon)"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Daily production health pass for {{APP_NAME}} (repository `{{CODE_DIR}}/{{REPO_NAME}}`, GCP project
|
|
10
|
+
`{{APP_SLUG}}-polyforms`, region `europe-west3`, backend Cloud Function gen 2 `{{APP_SLUG}}-server`,
|
|
11
|
+
request logs under `resource.type="cloud_run_revision"`). Analyse the last 24 hours of costs,
|
|
12
|
+
performance and monitoring, fix what is broken in code, push to main, watch CI. Report in the
|
|
13
|
+
person's language.
|
|
14
|
+
|
|
15
|
+
Error logs are out of scope: every `warn` and `error` goes to Sentry and to the `sentry-autofix`
|
|
16
|
+
routine.
|
|
17
|
+
|
|
18
|
+
Read the project's `CLAUDE.md` and the stack rules first and follow them. Work inline, never a
|
|
19
|
+
subagent. Other sessions may be editing the checkout: work in a worktree, or commit only your
|
|
20
|
+
files with an explicit pathspec; never stash, reset, switch branches, amend or force-push.
|
|
21
|
+
|
|
22
|
+
## 0. Preconditions
|
|
23
|
+
- Every `gcloud` / `bq` command is prefixed with `CLOUDSDK_CONFIG=$HOME/.config/claude-observer/gcloud`,
|
|
24
|
+
the read-only observer account's own configuration. Check it with `gcloud auth
|
|
25
|
+
print-access-token`. If it fails, never fall back to the person's login and never run `gcloud
|
|
26
|
+
auth login`: do the steps that need no GCP and put the failure at the top of the report.
|
|
27
|
+
- The observer account only reads. Never write to GCP with it.
|
|
28
|
+
- `git pull --ff-only` if the tree allows it; otherwise work on what is there and say so.
|
|
29
|
+
|
|
30
|
+
## 1. Costs
|
|
31
|
+
- Billing export: `bq query --project_id={{APP_SLUG}}-polyforms --use_legacy_sql=false` on
|
|
32
|
+
`{{BILLING_TABLE}}`, filtered on `project.id = "{{APP_SLUG}}-polyforms"`, cost plus credits per day
|
|
33
|
+
(`Europe/Paris`) and per service and SKU for the last 8 days. The export lags hours: yesterday
|
|
34
|
+
may be partial, say so.
|
|
35
|
+
- Flag any service above twice its 7-day daily average, any AI API above `{{AI_DAILY_ALERT}}` a
|
|
36
|
+
day, any new SKU. For Gemini, the grounded-search SKU is the expensive one; trace a spike through
|
|
37
|
+
the structured usage logs grouped by step and hour; `httpRequest.userAgent` and `remoteIp` on the
|
|
38
|
+
API route say whether one device caused it.
|
|
39
|
+
- A prepaid AI account answering 402 is critical: report it first, only the person can top up.
|
|
40
|
+
- Never read or print a key or a secret.
|
|
41
|
+
|
|
42
|
+
## 2. Performance
|
|
43
|
+
- From the API request logs of the last 24 h: p50, p95, p99 latency, 5xx rate, slowest hours.
|
|
44
|
+
Cloud Monitoring: request latencies, instance count, startup latencies, memory. Compare with
|
|
45
|
+
the previous day. Flag p95 above 2 s not explained by cold starts, a regression above 50 %,
|
|
46
|
+
memory near the limit. Cold starts are a figure, never an action item: scaling to zero is the
|
|
47
|
+
rule.
|
|
48
|
+
- Firestore usage when a cost or latency spike points there. Production data may be read to
|
|
49
|
+
explain a spike; never copy personal data into the report.
|
|
50
|
+
|
|
51
|
+
## 3. Monitoring
|
|
52
|
+
- CI: `gh run list --repo moifort/{{REPO_NAME}} --limit 30 --json databaseId,name,headSha,status,conclusion,createdAt`:
|
|
53
|
+
any failed run on main in 24 h, Deploy first, including its migrations step.
|
|
54
|
+
- Cloud Scheduler: `gcloud scheduler jobs list --project={{APP_SLUG}}-polyforms --location=europe-west3`,
|
|
55
|
+
state and last attempt of each job; a paused or failed job is a finding.
|
|
56
|
+
- The app configuration endpoint on the production host answers 200.
|
|
57
|
+
- App Store and TestFlight are out of scope.
|
|
58
|
+
|
|
59
|
+
## 4. Fix
|
|
60
|
+
For each real problem with a clear cause in code: fix it following the rules, verify (typecheck,
|
|
61
|
+
the three test families separately, linter, schema check if the schema changed, an app build for
|
|
62
|
+
app changes), commit without asking (conventional commits, English, explicit pathspec,
|
|
63
|
+
`Co-Authored-By` trailer), push directly to main (authorised for this routine), watch every CI
|
|
64
|
+
run for the commit until green, fix forward on failure. Never touch the changelogs, never deploy or
|
|
65
|
+
terraform from the machine, never run migrations by hand.
|
|
66
|
+
|
|
67
|
+
Only propose, never apply: anything that changes what users see or can do, any infrastructure,
|
|
68
|
+
Terraform or secret change, anything with an unclear cause, and anything that would raise paid API
|
|
69
|
+
calls (state its cost).
|
|
70
|
+
|
|
71
|
+
## 4b. The other routines
|
|
72
|
+
List the last run of every other routine of this account (Sentry triage, upgrades,
|
|
73
|
+
deprecations). One that missed two cadences is a finding at the top of the report.
|
|
74
|
+
|
|
75
|
+
## 5. Report (short)
|
|
76
|
+
One-line verdict first (all fine / fixed / action needed from you). Then Costs (yesterday's total
|
|
77
|
+
and per service versus the 7-day average), Performance (p50, p95, 5xx, instances), Monitoring
|
|
78
|
+
(CI, Deploy, scheduler). Then "Fixed" (hashes, CI result per workflow) and "To decide".
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: deprecations
|
|
3
|
+
scope: project
|
|
4
|
+
runtime: cloud
|
|
5
|
+
cadence: "0 9 1 * *"
|
|
6
|
+
title: "{{APP_NAME}} deprecations (monthly)"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Remove from {{APP_NAME}}'s API what no build on sale still asks for (repository `{{CODE_DIR}}/{{REPO_NAME}}`).
|
|
10
|
+
Follow the project's `CLAUDE.md` and the stack rules on API evolution. Report in the person's
|
|
11
|
+
language.
|
|
12
|
+
|
|
13
|
+
1. In a worktree of `origin/main`, run `bun run deprecations` with the store API key from
|
|
14
|
+
the environment (never printed).
|
|
15
|
+
2. For every element reported **removable**: remove it from the schema, the resolver, and the
|
|
16
|
+
code and tests only it used. Leave anything in grace, waiting, still asked, or to check by
|
|
17
|
+
hand; list those in the report.
|
|
18
|
+
3. Set the minimum supported build to the floor the script proposes. Never a higher value.
|
|
19
|
+
4. Verify as the rules prescribe (typecheck, the three test families separately, linter, schema
|
|
20
|
+
check), commit `refactor(api): remove deprecated fields no build on sale asks for`, push to
|
|
21
|
+
main (authorised for this routine), watch CI and Deploy to the end, fix forward on failure.
|
|
22
|
+
5. Report: what was removed, the new floor, what stays in grace and until when, anything to
|
|
23
|
+
check by hand.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: sentry-autofix
|
|
3
|
+
scope: machine
|
|
4
|
+
runtime: cloud
|
|
5
|
+
cadence: "0 */6 * * *"
|
|
6
|
+
title: Sentry autofix (every 6 hours)
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Fix the unresolved Sentry errors of every project of the organisation, push each fix to main, check
|
|
10
|
+
CI, and write a report in the person's language. This routine runs every six hours.
|
|
11
|
+
|
|
12
|
+
## Standing authorisation
|
|
13
|
+
The person authorised this routine to commit AND push its fixes directly to `main` without asking.
|
|
14
|
+
Every other rule of each project's `CLAUDE.md` and of the stack rules still applies: language,
|
|
15
|
+
commit style, changelog never touched, verification before commit, deprecate rather than remove an
|
|
16
|
+
API field, deploy only through CI, never terraform or migrate from the machine.
|
|
17
|
+
|
|
18
|
+
## Overlap guard (first)
|
|
19
|
+
A run starts on a fresh machine, so there is no lock file: before touching an issue, check that no
|
|
20
|
+
open pull request and no commit of the last 14 days on `main` carries `Fixes <SHORT-ID>`; if one
|
|
21
|
+
does, the issue is being handled, skip it.
|
|
22
|
+
|
|
23
|
+
## Sentry access
|
|
24
|
+
- Org `polyforms`, API `https://sentry.io/api/0/`.
|
|
25
|
+
- In the cloud, the token is a **network secret** of the cloud environment for the host
|
|
26
|
+
`sentry.io`: the proxy adds the `Authorization` header to every request to that host, so call
|
|
27
|
+
the API with `curl` **without** an authorization header and never look for a token in a file or
|
|
28
|
+
a variable. On a desktop run, the token is `SENTRY_AUTH_TOKEN` in the project's `.env`. Never
|
|
29
|
+
print it, never write it into a file or a commit.
|
|
30
|
+
- Logger extras appear under `.context` in the event JSON.
|
|
31
|
+
|
|
32
|
+
## Projects
|
|
33
|
+
The task's repositories are cloned at the start of the run: work in them. A Sentry project
|
|
34
|
+
`<name>-<platform>` maps to the repository `moifort/<name>`; a Sentry project whose
|
|
35
|
+
repository is not among the task's repositories is listed as unmapped in the report and skipped.
|
|
36
|
+
Add it to the task to cover it.
|
|
37
|
+
|
|
38
|
+
## Steps
|
|
39
|
+
1. Per project: `GET /projects/polyforms/{slug}/issues/?query=is:unresolved&statsPeriod=14d&sort=freq`.
|
|
40
|
+
Keep issues with events in the last 7 days. Skip external or transient causes (timeouts,
|
|
41
|
+
third-party 5xx, user cancellations) unless the code handles them wrongly.
|
|
42
|
+
2. Skip issues already fixed: after `git fetch origin`,
|
|
43
|
+
`git log origin/main --since="14 days ago" --grep="Fixes <SHORT-ID>"`; if found, retry the
|
|
44
|
+
resolve step only. A regression (`isRegression`) means the fix was wrong: investigate again.
|
|
45
|
+
3. Fetch the latest event (stack, message, `.context`, tags, release). Find the root cause in the
|
|
46
|
+
code. Without a confident root cause, do not guess: report "to investigate" with findings.
|
|
47
|
+
4. In the repository's clone (already on `main`), install dependencies, follow that project's
|
|
48
|
+
`CLAUDE.md` and the stack rules.
|
|
49
|
+
5. Fix with a regression test where the code is testable (test first). One commit per issue or
|
|
50
|
+
shared root cause, minimal.
|
|
51
|
+
6. Verify as the rules prescribe (typecheck, the three test families separately, linter, schema
|
|
52
|
+
check if the schema changed, an app build for app changes). Never commit a failing fix.
|
|
53
|
+
7. Commit: conventional commit, English, body with the root cause, trailer `Fixes <SHORT-ID>`,
|
|
54
|
+
then the `Co-Authored-By` trailer. Never touch the changelogs.
|
|
55
|
+
8. Push **to `main`**, not to a `claude/` branch: `git fetch origin && git rebase origin/main`,
|
|
56
|
+
then `git push origin HEAD:main`. A conflicting rebase is aborted and reported, never forced.
|
|
57
|
+
An **app (iOS) fix cannot be built here** (no Xcode): push it as a branch `claude/sentry-<id>`,
|
|
58
|
+
open a pull request with `gh pr create`, and let CI build it; the next pass merges it when CI
|
|
59
|
+
is green.
|
|
60
|
+
9. Watch CI for the pushed commit (`gh run list --json databaseId,headSha,name,status,conclusion`
|
|
61
|
+
filtered on `headSha`, then `gh run watch <id> --exit-status`). On failure, fix forward if the
|
|
62
|
+
cause is clear, otherwise revert, push the revert, report.
|
|
63
|
+
10. Once CI is green, Deploy included: `PUT /organizations/polyforms/issues/{id}/` with
|
|
64
|
+
`{"status":"resolvedInNextRelease"}`, never plain `resolved`.
|
|
65
|
+
11. Merge the pull requests opened by earlier passes whose CI is green (`gh pr list --author @me
|
|
66
|
+
--label sentry`), then resolve their issues as in step 10.
|
|
67
|
+
|
|
68
|
+
## Limits
|
|
69
|
+
- At most 3 fixes per pass, prioritised by event count times users affected.
|
|
70
|
+
- Never change infrastructure, secrets, CI workflows or migrations to silence an error; never
|
|
71
|
+
lower a log level to hide a bug; never delete data; never ignore an issue.
|
|
72
|
+
|
|
73
|
+
## Report (short, scannable)
|
|
74
|
+
Nothing found and nothing fixed: one line, plus any access problem. Otherwise per project: issues
|
|
75
|
+
examined, fixed (short id, root cause in one sentence, SHA, CI status, resolved or not), skipped
|
|
76
|
+
(reason), to investigate (leads). Access problems at the end.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: stack-upgrade
|
|
3
|
+
scope: machine
|
|
4
|
+
runtime: cloud
|
|
5
|
+
cadence: "0 7 * * *"
|
|
6
|
+
title: Upgrades (daily, after Renovate)
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Apply the stack package upgrades and the major dependency upgrades Renovate opened overnight, on
|
|
10
|
+
every project of the organisation. Report in the person's language.
|
|
11
|
+
|
|
12
|
+
## Discovery
|
|
13
|
+
For every repository under `{{CODE_DIR}}` whose `origin` points at `moifort/*`:
|
|
14
|
+
`gh pr list --repo <owner>/<name> --label stack --state open --json number,title,headRefName`.
|
|
15
|
+
A pull request bumping `@moifort/stack` follows "Stack pull request"; a pull request labelled
|
|
16
|
+
`major` follows "Major dependency"; anything else is reported, not touched.
|
|
17
|
+
|
|
18
|
+
## Stack pull request
|
|
19
|
+
1. Check out the pull request's branch in a worktree (`/tmp/stack-upgrade-<name>-<date>`),
|
|
20
|
+
install dependencies, and follow the `stack-upgrade` skill shipped by the package in
|
|
21
|
+
`.claude/skills/`: sync, migrations one by one with their checks, preflight, a comment on the
|
|
22
|
+
pull request with one line per migration, merge only when every migration is `safe`, applied or
|
|
23
|
+
not applicable, every check passes and CI is green. Otherwise label `stack:review` and leave it
|
|
24
|
+
open.
|
|
25
|
+
2. Remove the worktree.
|
|
26
|
+
|
|
27
|
+
## Major dependency
|
|
28
|
+
1. Check out the branch in a worktree, install. Read the dependency's release notes and migration
|
|
29
|
+
guide for every major between the old and the new version (`gh api` on the repository's
|
|
30
|
+
releases, or the package's CHANGELOG).
|
|
31
|
+
2. Apply the code changes the new major requires, in one commit per dependency, verified as the
|
|
32
|
+
rules prescribe. Respect the project's `allowedVersions` bounds: a pull request Renovate
|
|
33
|
+
should not have opened is closed with the reason.
|
|
34
|
+
3. Add the `staging` label so the Deploy workflow deploys the branch to staging; wait for it.
|
|
35
|
+
4. Merge when CI and the staging deploy are green. Otherwise comment with what blocks (the
|
|
36
|
+
failing job, the breaking change, the bound to add) and leave it open.
|
|
37
|
+
|
|
38
|
+
## Limits
|
|
39
|
+
- Never edit the package's rules from a consumer project; a rule that is wrong is reported.
|
|
40
|
+
- Never force-merge, never force-push, never touch a pull request without the `stack` label.
|
|
41
|
+
- At most one pull request per project per pass.
|
|
42
|
+
|
|
43
|
+
## Report
|
|
44
|
+
Per project: pull request, version from and to, migrations applied, not applicable, needing
|
|
45
|
+
review, merged or left open with the reason.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Collaboration
|
|
2
|
+
|
|
3
|
+
How Claude behaves on every project, whatever the stack.
|
|
4
|
+
|
|
5
|
+
## Short answers
|
|
6
|
+
|
|
7
|
+
Lead with the result and what blocks. No "what I built" section, no closing recap, no restating
|
|
8
|
+
the context: the person follows the work as it happens and knows their repository. Details come
|
|
9
|
+
when asked.
|
|
10
|
+
|
|
11
|
+
## Critical review before execution
|
|
12
|
+
|
|
13
|
+
Every proposed design or architecture choice gets a review as a senior infrastructure engineer
|
|
14
|
+
with a whole-system view: name the failure modes, say plainly when it is wrong or not worth
|
|
15
|
+
building, lead with the verdict. Before agreeing, check:
|
|
16
|
+
|
|
17
|
+
- what fails if an event is lost, a call times out, a process restarts;
|
|
18
|
+
- what the change delivers to the person using the product;
|
|
19
|
+
- whether it is new infrastructure for a single consumer;
|
|
20
|
+
- whether a cheaper design does the job (an idempotent write, a lazy reconciliation).
|
|
21
|
+
|
|
22
|
+
Never side with the person because they pushed back. Change position only on a better argument,
|
|
23
|
+
and say which argument changed it. Once the decision is taken, execute it fully and do not
|
|
24
|
+
relitigate it at the next task.
|
|
25
|
+
|
|
26
|
+
## Questions sized to the task
|
|
27
|
+
|
|
28
|
+
A simple request (a typo, a label, a one-line fix) is done at once, without a question and
|
|
29
|
+
without turning it into architecture. A request that involves decisions (several readings, a
|
|
30
|
+
trade-off, something that is hard to undo) gets the questions first, before any code. Ask when it
|
|
31
|
+
is needed, never as a reflex.
|
|
32
|
+
|
|
33
|
+
## Autonomy
|
|
34
|
+
|
|
35
|
+
Never stop to ask permission to continue. Stop for a real blocker or a question that changes a
|
|
36
|
+
lot, not for a checkpoint. A plan runs to its final result in one go; validation screenshots are
|
|
37
|
+
gathered and shown at the end, not used as a pause. Never ask permission to commit: see
|
|
38
|
+
[02-commits.md](02-commits.md).
|
|
39
|
+
|
|
40
|
+
## Run commands yourself, go to the end
|
|
41
|
+
|
|
42
|
+
A list of manual steps for the person to run is a failure, not a deliverable. Run every command
|
|
43
|
+
line yourself: checks, builds, fixes, verifications; drive the browser when the task is in a
|
|
44
|
+
browser; go to the end of the task. Only a command that needs the person's password, `sudo`, an
|
|
45
|
+
Apple sign-in or a physical action (plugging the phone) is theirs, and then say so plainly. When
|
|
46
|
+
about to end a message with "run this", run it instead and report.
|
|
47
|
+
|
|
48
|
+
Two things wait for a go, asked in one message that says what will be done and why: reading
|
|
49
|
+
production data, and anything that changes production (a deploy, a console setting, a store
|
|
50
|
+
submission). See [15-tooling.md](15-tooling.md#browser-and-doing-things-end-to-end).
|
|
51
|
+
|
|
52
|
+
## No agent, no plan mode, no spec by default
|
|
53
|
+
|
|
54
|
+
Exploration, review, debugging and planning happen inline, in the main conversation. By default:
|
|
55
|
+
no subagent, no parallel agent, no plan mode, no design document. Each of these happens only when
|
|
56
|
+
the person asks for it explicitly, for that task. A skill that requires dispatching an agent or
|
|
57
|
+
writing a document is applied inline, without them. This rule wins over any external procedure
|
|
58
|
+
that says otherwise.
|
|
59
|
+
|
|
60
|
+
## Where feedback goes
|
|
61
|
+
|
|
62
|
+
A correction given in conversation is written down in the same task, not remembered privately:
|
|
63
|
+
|
|
64
|
+
| What it is | Where it goes |
|
|
65
|
+
|---|---|
|
|
66
|
+
| A rule that would hold in any project | This package: the rule file, a version bump, a migration if code is affected |
|
|
67
|
+
| A fact of this project (an id, an account, a device, a certificate date) | The project's `project-context.md` |
|
|
68
|
+
| A decision specific to this project's product or data model | The project's `CLAUDE.md` |
|
|
69
|
+
|
|
70
|
+
Claude's private memory keeps neither rules nor project facts. What is not written in a repository
|
|
71
|
+
does not exist at the next session.
|
|
72
|
+
|
|
73
|
+
## Languages
|
|
74
|
+
|
|
75
|
+
| What is produced | Language |
|
|
76
|
+
|---|---|
|
|
77
|
+
| Code, comments, commit messages, technical documentation, specs, this package | English |
|
|
78
|
+
| Copy seen by the person using the product | `{{SOURCE_LANGUAGE}}`, then `{{SERVED_LANGUAGES}}` |
|
|
79
|
+
| Conversation | The person's language |
|
|
80
|
+
|
|
81
|
+
Never two languages in one commit message or one comment.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Workflow of a task
|
|
2
|
+
|
|
3
|
+
## The cycle
|
|
4
|
+
|
|
5
|
+
1. **Framing.** Understand the intent before coding. If two readings of the request lead to two
|
|
6
|
+
different pieces of work, ask now, not at the end.
|
|
7
|
+
2. **No spec, no plan mode by default.** A design document
|
|
8
|
+
(`docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md`) or a written plan is produced only when
|
|
9
|
+
the person asks for one. Without that, the plain explanation below is given in the conversation
|
|
10
|
+
and the work starts. A paid API call states its cost either way, see [13-ai.md](13-ai.md).
|
|
11
|
+
3. **Implementation**, inline, to the final result, without checkpoints.
|
|
12
|
+
4. **Verification**, below.
|
|
13
|
+
5. **Review** before commit, see [03-code-review.md](03-code-review.md).
|
|
14
|
+
6. **Commit**, see [02-commits.md](02-commits.md).
|
|
15
|
+
7. **Screenshot** when a screen changed, below.
|
|
16
|
+
|
|
17
|
+
## What an explanation or a plan contains
|
|
18
|
+
|
|
19
|
+
Before the work, in the conversation (or in the plan when one was asked), a plain section:
|
|
20
|
+
|
|
21
|
+
- the domains touched: created, modified, removed;
|
|
22
|
+
- how it works today, what it gives afterwards, ideally a table of before and after;
|
|
23
|
+
- the mechanisms at play, without jargon;
|
|
24
|
+
- what does not move;
|
|
25
|
+
- why the tasks are in this order.
|
|
26
|
+
|
|
27
|
+
Code blocks serve execution, the explanation serves the decision. Each task ends with a testable
|
|
28
|
+
deliverable and its verification command with the expected output.
|
|
29
|
+
|
|
30
|
+
## Verification before a commit
|
|
31
|
+
|
|
32
|
+
A claim of success without the command output behind it has no value. Before every commit:
|
|
33
|
+
|
|
34
|
+
| What was touched | What runs |
|
|
35
|
+
|---|---|
|
|
36
|
+
| Backend | `bun run typecheck` (after `bun run prepare` if routes changed), the three test families **separately** (see below), `bun run lint` |
|
|
37
|
+
| The API schema | `bun run schema:check`: a removal not preceded by a deprecation is refused, see [11-ops.md](11-ops.md#api-evolution) |
|
|
38
|
+
| The app | `bun run ios:build`; and after any on-screen text change, `bun run ios:strings` until it reports every string translated, see [08-copy.md](08-copy.md#localisation) |
|
|
39
|
+
|
|
40
|
+
The three test families run **separately** (`bun run test:unit`, `bun run test:int`,
|
|
41
|
+
`bun run test:feat`): a module mock declared in one test file serves every file of the same
|
|
42
|
+
run, so a single combined run passes while CI, which runs them apart, goes red.
|
|
43
|
+
|
|
44
|
+
## Mockups before a complex screen
|
|
45
|
+
|
|
46
|
+
A new feature with screens and real complexity (a new flow, several states, a layout nobody has
|
|
47
|
+
seen) starts with mockups, before any real code: quick throwaway renderings of the screens built
|
|
48
|
+
from fixtures (a preview or a harness screenshot, or a drawn mock when that is faster), sent for a
|
|
49
|
+
fast approval of the layout and the flow. Only once the mockups are approved does the real
|
|
50
|
+
implementation start. A small change to an existing screen skips this and goes straight to the
|
|
51
|
+
screenshot below.
|
|
52
|
+
|
|
53
|
+
## Screenshot after every UI change
|
|
54
|
+
|
|
55
|
+
Every request that changes a screen ends with a screenshot of the result, sent to the person,
|
|
56
|
+
followed by a wait for their approval. Without exception, and whatever the state of the simulator:
|
|
57
|
+
|
|
58
|
+
- "the simulator stops at sign-in" is never a reason to skip it. Feed the page its preview
|
|
59
|
+
fixtures through a temporary launch argument in the app entry point, build into a derived data
|
|
60
|
+
path unique to the session, install and launch, capture, then revert the harness before the
|
|
61
|
+
commit. The fixtures and previews stay.
|
|
62
|
+
- Before sending, check the design conventions of [10-ios.md](10-ios.md#design-conventions).
|
|
63
|
+
- Committing before approval is fine. Installing on the person's device before approval is not.
|
|
64
|
+
- The next task does not start until the screenshot is approved.
|
|
65
|
+
|
|
66
|
+
## Ideas and decisions
|
|
67
|
+
|
|
68
|
+
- **Ideas** that come up and are not built now go into the project's `docs/ideas.md`, one line
|
|
69
|
+
each with the date, so nothing said in passing is lost. Claude adds to it without being asked.
|
|
70
|
+
- **Architecture choices** get an ADR (architecture decision record) in `docs/adr/`, numbered
|
|
71
|
+
(`0007-flat-collections.md`): context, decision, alternatives rejected, consequences. Written
|
|
72
|
+
when the choice is made, proposed to the person before the code that depends on it.
|
|
73
|
+
|
|
74
|
+
(Shape to refine later; the rule is that both exist in every project.)
|
|
75
|
+
|
|
76
|
+
## Skills and tooling
|
|
77
|
+
|
|
78
|
+
Before starting, look at what is installed and invoke what matches the task. Never cite, impose
|
|
79
|
+
or document a skill without checking it exists on the machine. The skills this package ships are
|
|
80
|
+
in `.claude/skills/` after `sync`; the ones that matter are `domain`, `graphql`, `ios-feature`,
|
|
81
|
+
`appstore`, `stack-upgrade`.
|