@carecard/validate 3.1.26 → 3.2.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.
@@ -1,70 +1,57 @@
1
1
  ---
2
2
  name: github-pr-create-update
3
- description: 'Use only when the user explicitly asks for remote Git or GitHub PR work: pushing a branch, creating or updating a PR, or marking a PR ready from the current repository branch.'
3
+ description: 'Use only when the user explicitly asks for remote Git or GitHub PR work: pushing a branch, creating or updating a PR, or marking a PR ready from the current repository branch into development or main.'
4
4
  ---
5
5
 
6
6
  # Pull Request Create
7
7
 
8
8
  ## Purpose
9
9
 
10
- After the user explicitly asks for remote Git or GitHub PR work, create, update, verify, push, and mark ready a GitHub pull request from the current repository branch into origin/development.
10
+ After the user explicitly asks for remote Git or GitHub PR work, create,
11
+ update, verify, push, and mark ready a GitHub pull request from the current
12
+ repository branch into `development`, or into `main` when `development` is
13
+ absent.
11
14
 
12
15
  ## When To Use
13
16
 
14
- - Use only when the user explicitly asks to create, update, push for, or mark ready a GitHub pull request from the current repository branch.
17
+ - Use only when the user explicitly asks to create, update, push for, or mark
18
+ ready a GitHub pull request from the current repository branch.
15
19
 
16
20
  ## When Not To Use
17
21
 
18
- - Do not use for merging or deleting an already-approved pull request; use the merge cleanup skill.
22
+ - Do not use for merging or deleting an already-approved pull request; use the
23
+ merge cleanup skill.
19
24
  - Do not use for ordinary local commits that do not involve GitHub PR work.
20
25
 
21
26
  ## Remote Git Operations Guardrail
22
27
 
23
- Do not run remote Git or GitHub operations unless the current user request explicitly asks for them. This includes `git fetch`, `git pull`, `git push`, `git push --delete`, remote branch cleanup, GitHub API calls, and any `gh pr` command that creates, updates, readies, merges, closes, or cleans up a pull request. Do not infer permission from branch names, validation needs, prior workflow habits, or convenience; ask first when remote state would help but was not requested.
24
-
25
- ## Relevant Files And Directories
26
-
27
- - Git branch state in this repository
28
- - GitHub pull requests viewed with `gh`
29
- - repository validation commands and `.husky` scripts
30
-
31
- ## Coding Principles
32
-
33
- - Preserve the repository structure, naming style, module system, and local helper patterns.
34
- - Prefer readable, maintainable code with meaningful function, variable, file, and test names.
35
- - Avoid new dependencies unless the existing stack cannot reasonably solve the task and the user confirms the tradeoff.
36
-
37
- ## Testing Expectations
38
-
39
- - Run repository validation before PR creation or merge when code behavior changed.
40
- - Confirm the branch is clean except intended changes before finishing.
41
-
42
- ## Safety Constraints
43
-
44
- - Do not edit generated output, dependency folders, logs, coverage, dist, or build artifacts unless the task requires it.
45
- - Do not revert or overwrite user changes; stage only requested skill or instruction files.
46
- - Never suppress errors, lint failures, type failures, security failures, or failing tests; fix the underlying issue or report the blocker.
47
-
48
- ## Commit Continuation Rule
49
-
50
- Do not amend commits unless the user explicitly asks. If
51
- hook, formatter, documentation, skill, validation, or review follow-up changes
52
- appear after a commit, stage only the intended files and make a new commit with
53
- a clear message.
28
+ Do not run remote Git or GitHub operations unless the current user request
29
+ explicitly asks for them. This includes `git fetch`, `git pull`, `git push`,
30
+ `git push --delete`, remote branch cleanup, GitHub API calls, and any `gh pr`
31
+ command that creates, updates, readies, merges, closes, or cleans up a pull
32
+ request. Do not infer permission from branch names, validation needs, prior
33
+ workflow habits, or convenience; ask first when remote state would help but was
34
+ not requested.
54
35
 
55
36
  ## Scope
56
37
 
57
38
  Use this skill from the root of the repository whose current branch contains
58
- the intended PR changes. The repository must have `origin/development`, and
59
- GitHub CLI must be available and authenticated.
39
+ the intended PR changes. The repository must have `origin/development` or
40
+ `origin/main`, and GitHub CLI must be available and authenticated.
41
+
42
+ Default terms:
43
+
44
+ - Base branch: `development` when `origin/development` exists; otherwise
45
+ `main` when `origin/main` exists.
46
+ - Source branch: the current branch unless the user names another branch.
60
47
 
61
48
  Do not continue automatically when:
62
49
 
63
- - The current branch is `development`, `main`, `master`, or detached.
50
+ - The source branch is `development`, `main`, `master`, or detached.
64
51
  - The working tree has uncommitted changes. Explain that a PR only includes
65
52
  committed changes and ask the user whether to commit or stash them.
66
53
  - `gh auth status` fails.
67
- - `origin/development` cannot be fetched.
54
+ - Neither `origin/development` nor `origin/main` exists.
68
55
 
69
56
  ## Workflow
70
57
 
@@ -80,30 +67,26 @@ Do not continue automatically when:
80
67
  gh auth status
81
68
  ```
82
69
 
83
- 2. Pull a fresh development reference from the remote without leaving the
84
- source branch:
70
+ 2. Select the pull request base branch. Prefer `development`; use `main` only
71
+ when `origin/development` is absent:
85
72
 
86
73
  ```sh
87
- git fetch origin development --prune
88
- ```
89
-
90
- Use `origin/development` as the source of truth. If a local `development`
91
- branch exists and is not checked out, it may be fast-forwarded to match the
92
- remote without forcing history:
93
-
94
- ```sh
95
- git fetch origin development:development
74
+ if git ls-remote --exit-code --heads origin development >/dev/null 2>&1; then
75
+ base="development"
76
+ elif git ls-remote --exit-code --heads origin main >/dev/null 2>&1; then
77
+ base="main"
78
+ else
79
+ echo "No origin/development or origin/main branch exists."
80
+ exit 1
81
+ fi
82
+ git fetch origin "$base" --prune
96
83
  ```
97
84
 
98
- If that local branch update fails because `development` has diverged or is
99
- checked out in another worktree, do not force it. Continue using
100
- `origin/development` for validation.
101
-
102
- 3. Check whether the current branch can merge with latest development without
85
+ 3. Check whether the current branch can merge with the latest base without
103
86
  changing the worktree:
104
87
 
105
88
  ```sh
106
- if git merge-tree --write-tree HEAD origin/development >/tmp/pull-request-create-merge-tree.out
89
+ if git merge-tree --write-tree HEAD "origin/$base" >/tmp/pull-request-create-merge-tree.out
107
90
  then
108
91
  merge_conflict_detected=false
109
92
  else
@@ -111,11 +94,11 @@ Do not continue automatically when:
111
94
  fi
112
95
  ```
113
96
 
114
- 4. If a merge conflict is detected, try rebasing on latest development:
97
+ 4. If a merge conflict is detected, try rebasing on the latest base:
115
98
 
116
99
  ```sh
117
100
  if [ "$merge_conflict_detected" = true ]; then
118
- if git rebase origin/development; then
101
+ if git rebase "origin/$base"; then
119
102
  git push --force-with-lease -u origin "$branch"
120
103
  else
121
104
  git rebase --abort
@@ -127,62 +110,58 @@ Do not continue automatically when:
127
110
  fi
128
111
  ```
129
112
 
130
- Do not resolve rebase conflicts unless the user explicitly asks. If the
131
- rebase fails, abort it and stop.
113
+ Do not use `--no-verify`; pre-push hooks must run. Do not resolve rebase
114
+ conflicts unless the user explicitly asks.
132
115
 
133
- 5. Inspect the branch changes before writing the PR title:
116
+ 5. Verify the remote branch matches local `HEAD`:
134
117
 
135
118
  ```sh
136
- git log --reverse --format='%s' origin/development..HEAD
137
- git diff --stat origin/development...HEAD
119
+ local_sha="$(git rev-parse HEAD)"
120
+ remote_sha="$(git ls-remote --heads origin "$branch" | awk '{print $1}')"
121
+ test "$local_sha" = "$remote_sha"
138
122
  ```
139
123
 
140
- Create a concise, descriptive title from the actual changes. Remove words
141
- such as `draft`, `[draft]`, `Draft:`, and `WIP`. Do not leave a generic title
142
- such as "updates", "changes", or "draft PR".
124
+ 6. Inspect the branch changes before writing the PR title:
143
125
 
144
- 6. Reuse an existing open PR for this branch when present:
126
+ ```sh
127
+ git log --reverse --format='%s' "origin/$base..HEAD"
128
+ git diff --stat "origin/$base...HEAD"
129
+ ```
130
+
131
+ 7. Reuse an existing open PR for this exact branch/base pair when present:
145
132
 
146
133
  ```sh
147
134
  pr_number="$(gh pr list \
148
135
  --head "$branch" \
149
- --base development \
136
+ --base "$base" \
150
137
  --state open \
151
138
  --json number \
152
139
  --jq '.[0].number // empty')"
153
140
  ```
154
141
 
155
- 7. If there is no PR, create one against `development` and capture the new PR
156
- number:
142
+ 8. If there is no PR, create one against the selected base and capture the new
143
+ PR number:
157
144
 
158
145
  ```sh
159
146
  pr_url="$(gh pr create \
160
- --base development \
147
+ --base "$base" \
161
148
  --head "$branch" \
162
149
  --title "$title" \
163
150
  --body "$body")"
164
151
  pr_number="$(gh pr view "$pr_url" --json number --jq '.number')"
165
152
  ```
166
153
 
167
- Keep the body factual. Mention the main changes and validation commands
168
- that were actually run.
169
-
170
- 8. If a PR exists, mark it ready when it is a draft:
154
+ 9. If a PR exists, mark it ready when it is a draft, then keep the title
155
+ descriptive:
171
156
 
172
157
  ```sh
173
158
  is_draft="$(gh pr view "$pr_number" --json isDraft --jq '.isDraft')"
174
159
  if [ "$is_draft" = "true" ]; then
175
160
  gh pr ready "$pr_number"
176
161
  fi
177
- ```
178
-
179
- 9. Update the PR title after create/reuse so it is descriptive and contains no
180
- draft wording:
181
-
182
- ```sh
183
162
  gh pr edit "$pr_number" --title "$title"
184
163
  ```
185
164
 
186
- 10. Final response should include the PR URL, whether a rebase was performed,
187
- whether an existing PR was reused or marked ready, and any validation that
188
- could not be run.
165
+ 10. Final response should include the PR URL, selected base branch, whether a
166
+ rebase was performed, whether an existing PR was reused or marked ready,
167
+ and any validation that could not be run.
@@ -1,56 +1,38 @@
1
1
  ---
2
2
  name: github-pr-merge-cleanup
3
- description: 'Use only when the user explicitly asks for remote Git or GitHub PR work: reviewing remote mergeability, validating, merging, closing, deleting, or cleaning up a pull request branch.'
3
+ description: 'Use only when the user explicitly asks for remote Git or GitHub PR work: pushing a branch, creating a missing PR, reviewing mergeability, validating, merging, deleting, or cleaning up a pull request branch.'
4
4
  ---
5
5
 
6
6
  # Pull Request Merge Close
7
7
 
8
8
  ## Purpose
9
9
 
10
- After the user explicitly asks for remote Git or GitHub PR work, review, validate, merge, close, delete branch, and clean local state for a GitHub pull request targeting origin/development.
10
+ After the user explicitly asks for remote Git or GitHub PR work, push the
11
+ branch, create a missing PR when needed, review, validate, merge, delete the
12
+ branch when allowed, and clean local state for a GitHub pull request targeting
13
+ `development`, or `main` when `development` is absent.
11
14
 
12
15
  ## When To Use
13
16
 
14
- - Use only when the user explicitly asks to review mergeability, validate, merge, close, or clean up a GitHub pull request branch.
17
+ - Use only when the user explicitly asks to push, review mergeability,
18
+ validate, merge, close, or clean up a GitHub pull request branch.
15
19
 
16
20
  ## When Not To Use
17
21
 
18
- - Do not use for creating a new pull request; use the PR create/update skill.
19
- - Do not use when the user only asks for local code changes without PR merge work.
22
+ - Do not use for PR-only creation/update work without a merge request; use the
23
+ PR create/update skill.
24
+ - Do not use when the user only asks for local code changes without PR merge
25
+ work.
20
26
 
21
27
  ## Remote Git Operations Guardrail
22
28
 
23
- Do not run remote Git or GitHub operations unless the current user request explicitly asks for them. This includes `git fetch`, `git pull`, `git push`, `git push --delete`, remote branch cleanup, GitHub API calls, and any `gh pr` command that creates, updates, readies, merges, closes, or cleans up a pull request. Do not infer permission from branch names, validation needs, prior workflow habits, or convenience; ask first when remote state would help but was not requested.
24
-
25
- ## Relevant Files And Directories
26
-
27
- - Git branch state in this repository
28
- - GitHub pull requests viewed with `gh`
29
- - repository validation commands and `.husky` scripts
30
-
31
- ## Coding Principles
32
-
33
- - Preserve the repository structure, naming style, module system, and local helper patterns.
34
- - Prefer readable, maintainable code with meaningful function, variable, file, and test names.
35
- - Avoid new dependencies unless the existing stack cannot reasonably solve the task and the user confirms the tradeoff.
36
-
37
- ## Testing Expectations
38
-
39
- - Run repository validation before PR creation or merge when code behavior changed.
40
- - Confirm the branch is clean except intended changes before finishing.
41
-
42
- ## Safety Constraints
43
-
44
- - Do not edit generated output, dependency folders, logs, coverage, dist, or build artifacts unless the task requires it.
45
- - Do not revert or overwrite user changes; stage only requested skill or instruction files.
46
- - Never suppress errors, lint failures, type failures, security failures, or failing tests; fix the underlying issue or report the blocker.
47
-
48
- ## Commit Continuation Rule
49
-
50
- Do not amend commits unless the user explicitly asks. If
51
- hook, formatter, documentation, skill, validation, or review follow-up changes
52
- appear after a commit, stage only the intended files and make a new commit with
53
- a clear message.
29
+ Do not run remote Git or GitHub operations unless the current user request
30
+ explicitly asks for them. This includes `git fetch`, `git pull`, `git push`,
31
+ `git push --delete`, remote branch cleanup, GitHub API calls, and any `gh pr`
32
+ command that creates, updates, readies, merges, closes, or cleans up a pull
33
+ request. Do not infer permission from branch names, validation needs, prior
34
+ workflow habits, or convenience; ask first when remote state would help but was
35
+ not requested.
54
36
 
55
37
  ## Scope
56
38
 
@@ -60,8 +42,8 @@ branch available locally or on `origin`.
60
42
 
61
43
  Default terms:
62
44
 
63
- - Base branch: `development` when `origin/development` exists, otherwise the
64
- repository default branch.
45
+ - Base branch: `development` when `origin/development` exists, otherwise
46
+ `main` when `origin/main` exists.
65
47
  - Target branch: the current branch unless the user names another branch.
66
48
  - Pull request: the open PR whose head is the target branch and whose base is
67
49
  the base branch.
@@ -72,35 +54,86 @@ Do not continue automatically when:
72
54
  - The target branch is detached or is the base branch.
73
55
  - The working tree has uncommitted changes that are not part of the requested
74
56
  PR cleanup.
75
- - No open PR exists for the target branch.
57
+ - Neither `origin/development` nor `origin/main` exists.
76
58
  - A rebase or validation fix would require behavior changes instead of coding
77
59
  criteria cleanup.
78
60
 
61
+ If no open pull request exists for the target branch and selected base, create
62
+ one as part of the merge workflow when the user requested push/PR/merge
63
+ completion.
64
+
79
65
  ## Workflow
80
66
 
81
- 1. Capture the base branch, target branch, PR number, and protection state:
67
+ 1. Capture the base branch, target branch, and authentication state:
82
68
 
83
69
  ```sh
84
70
  gh auth status
85
- base="development"
86
- git ls-remote --exit-code --heads origin development >/dev/null 2>&1 || \
87
- base="$(gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name')"
71
+ if git ls-remote --exit-code --heads origin development >/dev/null 2>&1; then
72
+ base="development"
73
+ elif git ls-remote --exit-code --heads origin main >/dev/null 2>&1; then
74
+ base="main"
75
+ else
76
+ echo "No origin/development or origin/main branch exists."
77
+ exit 1
78
+ fi
88
79
  target_branch="$(git branch --show-current)"
89
80
  test -n "$target_branch"
90
81
  test "$target_branch" != "$base"
91
82
  git status --short
83
+ ```
84
+
85
+ If the user names a target branch, use that branch instead of the current
86
+ branch. If the target branch is not local but exists on `origin`, create a
87
+ local branch from the remote head before continuing:
88
+
89
+ ```sh
90
+ if ! git show-ref --verify --quiet "refs/heads/$target_branch"; then
91
+ git fetch origin "$target_branch:$target_branch"
92
+ fi
93
+ git switch "$target_branch"
92
94
  git fetch origin "$base" --prune
93
- pr_number="$(gh pr list --head "$target_branch" --base "$base" --state open --json number --jq '.[0].number // empty')"
94
- test -n "$pr_number"
95
- protected="$(gh api "repos/{owner}/{repo}/branches/$target_branch" --jq '.protected' 2>/dev/null || echo false)"
96
95
  ```
97
96
 
98
- If the PR head branch is not local, create a local branch from the remote
99
- head before continuing.
97
+ 2. Push the target branch to the same remote branch name. Do not use
98
+ `--no-verify`; pre-push hooks must run.
100
99
 
101
- 2. Check mergeability before changing history:
100
+ ```sh
101
+ git push -u origin "$target_branch"
102
+ local_sha="$(git rev-parse HEAD)"
103
+ remote_sha="$(git ls-remote --heads origin "$target_branch" | awk '{print $1}')"
104
+ test "$local_sha" = "$remote_sha"
105
+ ```
106
+
107
+ 3. Reuse an existing open PR for this exact branch/base pair, or create one
108
+ when none exists:
109
+
110
+ ```sh
111
+ pr_number="$(gh pr list \
112
+ --head "$target_branch" \
113
+ --base "$base" \
114
+ --state open \
115
+ --json number \
116
+ --jq '.[0].number // empty')"
117
+
118
+ if [ -z "$pr_number" ]; then
119
+ git log --reverse --format='%s' "origin/$base..HEAD"
120
+ git diff --stat "origin/$base...HEAD"
121
+ pr_url="$(gh pr create \
122
+ --base "$base" \
123
+ --head "$target_branch" \
124
+ --title "$title" \
125
+ --body "$body")"
126
+ pr_number="$(gh pr view "$pr_url" --json number --jq '.number')"
127
+ fi
128
+ ```
129
+
130
+ 4. Mark draft PRs ready and check mergeability before changing history:
102
131
 
103
132
  ```sh
133
+ is_draft="$(gh pr view "$pr_number" --json isDraft --jq '.isDraft')"
134
+ if [ "$is_draft" = "true" ]; then
135
+ gh pr ready "$pr_number"
136
+ fi
104
137
  gh pr view "$pr_number" --json mergeStateStatus,mergeable,headRefName,baseRefName
105
138
  if git merge-tree --write-tree HEAD "origin/$base" >/tmp/pull-request-merge-close-merge-tree.out
106
139
  then
@@ -110,7 +143,7 @@ Do not continue automatically when:
110
143
  fi
111
144
  ```
112
145
 
113
- 3. If a merge conflict is detected, rebase the target branch on the fresh base
146
+ 5. If a merge conflict is detected, rebase the target branch on the fresh base
114
147
  branch. Abort and stop if the rebase conflicts:
115
148
 
116
149
  ```sh
@@ -125,33 +158,14 @@ Do not continue automatically when:
125
158
  fi
126
159
  ```
127
160
 
128
- Do not resolve rebase conflicts unless the user explicitly asks.
129
-
130
- 4. Load and apply all relevant repository skills before merging:
131
- - Read the repository's `.agents/skills/**/SKILL.md` files that apply to the
132
- changed code, plus shared workspace standards when present.
133
- - Compare the target branch against the base with
134
- `git diff --stat "origin/$base...HEAD"` and inspect changed files.
135
- - Check whether the target branch satisfies the applicable coding,
136
- architecture, validation, security, and style criteria from those skills.
137
- - Run the validation commands required by the skills and repository hooks.
138
- - If criteria are not met and the fix does not change functionality, make the
139
- minimal cleanup, stage only intended files, commit to the target branch,
140
- and push the target branch.
141
- - If meeting the criteria would change behavior, stop and report the gap.
142
-
143
- 5. Confirm the PR is still mergeable after validation changes:
144
-
145
- ```sh
146
- git fetch origin "$base" --prune
147
- git merge-tree --write-tree HEAD "origin/$base" >/tmp/pull-request-merge-close-final-merge-tree.out
148
- gh pr checks "$pr_number"
149
- ```
161
+ 6. Load and apply all relevant repository skills before merging, then confirm
162
+ the PR is still mergeable after any validation changes.
150
163
 
151
- 6. Merge the PR with GitHub CLI. Delete the remote target branch only when it is
164
+ 7. Merge the PR with GitHub CLI. Delete the remote target branch only when it is
152
165
  not protected:
153
166
 
154
167
  ```sh
168
+ protected="$(gh api "repos/{owner}/{repo}/branches/$target_branch" --jq '.protected' 2>/dev/null || echo false)"
155
169
  if [ "$protected" = true ]; then
156
170
  gh pr merge "$pr_number" --squash --admin
157
171
  else
@@ -159,17 +173,27 @@ Do not continue automatically when:
159
173
  fi
160
174
  ```
161
175
 
162
- 7. Clean up the local repository after merge:
176
+ 8. If the merge succeeded and the remote branch still exists while unprotected,
177
+ delete it explicitly:
163
178
 
164
179
  ```sh
165
- git fetch origin "$base" --prune
180
+ if [ "$protected" != true ] && git ls-remote --exit-code --heads origin "$target_branch" >/dev/null 2>&1; then
181
+ git push origin --delete "$target_branch"
182
+ fi
183
+ ```
184
+
185
+ 9. Clean up the local repository after merge:
186
+
187
+ ```sh
188
+ git fetch origin --prune
166
189
  git switch "$base"
167
190
  git pull --ff-only origin "$base"
168
191
  git branch -d "$target_branch" || git branch -D "$target_branch"
169
192
  git ls-remote --heads origin "$target_branch"
170
193
  ```
171
194
 
172
- 8. Final response should include the PR URL, whether a rebase was performed,
173
- what validation and skill checks ran, whether any cleanup commit was added,
174
- whether the remote target branch was deleted or protected, and whether local
175
- development is up to date.
195
+ 10. Final response should include the PR URL, selected base branch, whether a
196
+ rebase was performed, what validation and skill checks ran, whether any
197
+ cleanup commit was added, whether the remote target branch was deleted or
198
+ protected, whether the local target branch was deleted, and whether local
199
+ base branch is up to date.
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: npm-package-flow
3
+ description: 'Use in pkg-* repositories when publishable package code changes require a version bump, GitHub development/main squash-merge flow, npm publication, and uncommitted consumer package updates in ms-* and app-dashboard.'
4
+ ---
5
+
6
+ # npm Package Flow
7
+
8
+ ## Purpose
9
+
10
+ Use this skill in a `pkg-*` repository when package code changes need to be
11
+ published to npm and propagated to CareCard consumers.
12
+
13
+ The package name is the `name` field in the repository `package.json`.
14
+
15
+ ## Publishable vs Non-Publishable Changes
16
+
17
+ Publishable package changes include runtime source changes, public exports,
18
+ TypeScript declarations, package metadata that affects consumers, security
19
+ behavior, or dependency behavior that changes the package contract.
20
+
21
+ The following are not publishable package changes by themselves:
22
+
23
+ - Skills or `.agents` guidance.
24
+ - Documentation and README updates.
25
+ - Tests, fixtures, mocks, snapshots, or validation-only changes.
26
+ - Formatting-only changes and comments.
27
+
28
+ For non-publishable changes, do not bump the package version, publish to npm,
29
+ or update `ms-*` and `app-dashboard` package versions.
30
+
31
+ ## Required Flow For Publishable Changes
32
+
33
+ Run this workflow only when the user explicitly asks for package publication,
34
+ remote GitHub merge work, or the full package-flow completion. Remote Git and
35
+ GitHub operations must not be inferred.
36
+
37
+ 1. Finish package code, tests, documentation, and skill updates inside the
38
+ current `pkg-*` repository.
39
+ 2. Bump the package version in `package.json` and `package-lock.json` according
40
+ to the user request or the package change scope.
41
+ 3. Run the package's required tests, lint, type checks, and every direct Husky
42
+ script. Fix failures before continuing.
43
+ 4. Commit the package changes to the current branch.
44
+ 5. Push the current branch, create or reuse the PR into `development`,
45
+ squash-merge it with administrator privileges, and delete the merged branch.
46
+ 6. Create a new merge branch from the updated `development` branch and use that
47
+ branch to open a PR into `main`.
48
+ 7. Squash-merge the merge branch into `main` with administrator privileges and
49
+ delete the merge branch. This `main` merge publishes the package.
50
+ 8. Confirm publication with `npm view <package-name>@<version> version`.
51
+ 9. Check out a fresh local branch with the same name as the deleted working
52
+ branch from the updated `development` branch.
53
+ 10. Update the new `@carecard/...` package version in `app-dashboard` and in
54
+ only the `ms-*` repositories that already declare the package, plus any
55
+ explicitly intended new consumers.
56
+ 11. Run `npm install` and relevant validation in each updated consumer.
57
+ 12. Do not commit the `ms-*` or `app-dashboard` consumer updates unless the user
58
+ explicitly asks.
59
+
60
+ ## Consumer Update Rules
61
+
62
+ - Discover existing consumers by checking each target repository `package.json`
63
+ for the published package name.
64
+ - Install exact package versions, for example
65
+ `npm install <package-name>@<version> --save-exact`.
66
+ - Keep consumer updates local and uncommitted unless the user gives a separate
67
+ commit or PR instruction.
68
+ - If a consumer should become a new dependency, require explicit user intent for
69
+ that repository.
70
+
71
+ ## Reporting
72
+
73
+ Report the package name, published version, development PR, main PR, npm
74
+ publication check, consumer repositories updated, validation commands run, and
75
+ any consumer updates intentionally left uncommitted.
@@ -0,0 +1,5 @@
1
+ interface:
2
+ display_name: 'npm Package Flow'
3
+ short_description: 'Use in pkg-* repositories when publishable @carecard package code changes need npm publication and consumer updates.'
4
+ brand_color: '#0F766E'
5
+ default_prompt: 'Use $npm-package-flow when this task matches the skill scope.'
@@ -3,6 +3,7 @@ name: Publish to npm
3
3
  on:
4
4
  push:
5
5
  branches:
6
+ - development
6
7
  - main
7
8
 
8
9
  permissions:
@@ -22,13 +23,39 @@ jobs:
22
23
  node-version: '25'
23
24
  registry-url: 'https://registry.npmjs.org'
24
25
 
26
+ - name: Remove unsupported npm auth config
27
+ run: |
28
+ npm_config_file="${NPM_CONFIG_USERCONFIG:-$HOME/.npmrc}"
29
+ if [ -f "$npm_config_file" ]; then
30
+ sed -i '/^[[:space:]]*always-auth[[:space:]]*=.*/d' "$npm_config_file"
31
+ fi
32
+
25
33
  - name: Install dependencies
26
34
  # Added HUSKY=0 to prevent the husky error in logs
27
35
  run: npm ci
28
36
  env:
29
37
  HUSKY: 0
30
38
 
39
+ - name: Check package version
40
+ id: package-version
41
+ run: |
42
+ package_name="$(node -p "require('./package.json').name")"
43
+ package_version="$(node -p "require('./package.json').version")"
44
+
45
+ echo "package_name=${package_name}" >> "$GITHUB_OUTPUT"
46
+ echo "package_version=${package_version}" >> "$GITHUB_OUTPUT"
47
+
48
+ if npm view "${package_name}@${package_version}" version >/dev/null 2>&1; then
49
+ echo "${package_name}@${package_version} is already published."
50
+ echo "should_publish=false" >> "$GITHUB_OUTPUT"
51
+ else
52
+ echo "${package_name}@${package_version} is not published yet."
53
+ echo "should_publish=true" >> "$GITHUB_OUTPUT"
54
+ fi
55
+
31
56
  - name: Publish to npm
57
+ if: steps.package-version.outputs.should_publish == 'true'
32
58
  run: npm publish --provenance --access public
33
59
  env:
60
+ HUSKY: 0
34
61
  NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/.husky/pre-commit CHANGED
@@ -1,3 +1,5 @@
1
+ npm run lint:fix
2
+ npm run format
3
+
1
4
  # Run tests
2
- npm run test
3
- npm run test:types
5
+ npm run test:All
package/index.d.ts CHANGED
@@ -181,6 +181,8 @@ export const isValidDomainName: BoolValidator;
181
181
  export const isValidTimestampzString: BoolValidator;
182
182
  /** Checks if the string is a valid ISO 8601 timestamp without time zone. */
183
183
  export const isValidTimestampString: BoolValidator;
184
+ /** Checks if the string is a valid ISO date in YYYY-MM-DD format. */
185
+ export const isValidDateString: BoolValidator;
184
186
  /** Checks if the string is a valid URL. */
185
187
  export const isValidUrl: BoolValidator;
186
188
  /** Checks if the array contains only safe strings. */
@@ -223,6 +225,7 @@ export const validate: {
223
225
  isValidDomainName: typeof isValidDomainName;
224
226
  isValidTimestampzString: typeof isValidTimestampzString;
225
227
  isValidTimestampString: typeof isValidTimestampString;
228
+ isValidDateString: typeof isValidDateString;
226
229
  isValidUrl: typeof isValidUrl;
227
230
  isValidArrayOfStrings: typeof isValidArrayOfStrings;
228
231
  };
package/lib/validate.js CHANGED
@@ -207,6 +207,21 @@ const isValidTimestampString = str => {
207
207
  return timestampRegex.test(str) && !isNaN(Date.parse(str));
208
208
  };
209
209
 
210
+ const isValidDateString = str => {
211
+ if (typeof str !== 'string' || str.length === 0 || str.length > 10) return false;
212
+
213
+ const dateRegex = /^(\d{4})-(\d{2})-(\d{2})$/;
214
+ const match = str.match(dateRegex);
215
+ if (!match) return false;
216
+
217
+ const year = Number(match[1]);
218
+ const month = Number(match[2]);
219
+ const day = Number(match[3]);
220
+ const date = new Date(Date.UTC(year, month - 1, day));
221
+
222
+ return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
223
+ };
224
+
210
225
  const isValidUrl = url => {
211
226
  if (typeof url !== 'string' || url.length === 0 || url.length > 2048) return false;
212
227
  try {
@@ -253,6 +268,7 @@ module.exports = {
253
268
  isValidDomainName,
254
269
  isValidTimestampzString,
255
270
  isValidTimestampString,
271
+ isValidDateString,
256
272
  isValidUrl,
257
273
  isValidArrayOfStrings,
258
274
  };
@@ -16,6 +16,7 @@ const {
16
16
  isValidDomainName,
17
17
  isValidTimestampzString,
18
18
  isValidTimestampString,
19
+ isValidDateString,
19
20
  isBoolValue,
20
21
  isValidUrl,
21
22
  isValidArrayOfStrings,
@@ -60,8 +61,10 @@ function validateProperties(obj = {}) {
60
61
  case 'programName':
61
62
  case 'role_name':
62
63
  case 'roleName':
63
- case 'document_type':
64
- case 'documentType':
64
+ case 'document_name':
65
+ case 'documentName':
66
+ case 'document_required_for_role_name':
67
+ case 'documentRequiredForRoleName':
65
68
  case 'reason':
66
69
  case 'entity_type':
67
70
  case 'entityType':
@@ -91,6 +94,8 @@ function validateProperties(obj = {}) {
91
94
 
92
95
  case 'is_primary':
93
96
  case 'isPrimary':
97
+ case 'document_optional':
98
+ case 'documentOptional':
94
99
  if (isBoolValue(value)) {
95
100
  returnObj[key] = value;
96
101
  }
@@ -161,6 +166,10 @@ function validateProperties(obj = {}) {
161
166
  case 'campusId':
162
167
  case 'program_id':
163
168
  case 'programId':
169
+ case 'program_requirement_document_id':
170
+ case 'programRequirementDocumentId':
171
+ case 'program_document_id':
172
+ case 'programDocumentId':
164
173
  case 'id':
165
174
  case 'institution_id':
166
175
  case 'institutionId':
@@ -184,6 +193,10 @@ function validateProperties(obj = {}) {
184
193
  break;
185
194
  case 'requested_by_name':
186
195
  case 'requestedByName':
196
+ case 'document_description':
197
+ case 'documentDescription':
198
+ case 'nick_name':
199
+ case 'nickName':
187
200
  case 'requested_by_email':
188
201
  case 'requestedByEmail':
189
202
  case 'requested_by_phone':
@@ -283,6 +296,20 @@ function validateProperties(obj = {}) {
283
296
  returnObj[key] = value;
284
297
  }
285
298
  break;
299
+ case 'document_required_by_date':
300
+ case 'documentRequiredByDate':
301
+ case 'effective_start_date':
302
+ case 'effectiveStartDate':
303
+ case 'effective_end_date':
304
+ case 'effectiveEndDate':
305
+ case 'valid_until_date':
306
+ case 'validUntilDate':
307
+ case 'renew_date':
308
+ case 'renewDate':
309
+ if (isValidDateString(value)) {
310
+ returnObj[key] = value;
311
+ }
312
+ break;
286
313
  case 'active':
287
314
  if (isBoolValue(value)) {
288
315
  returnObj[key] = value;
@@ -16,6 +16,8 @@ const MAX_NESTING_DEPTH = 5;
16
16
  * adversarial inputs.
17
17
  */
18
18
  const MAX_KEYS_PER_CALL = 5000;
19
+ const DEFAULT_FLATTEN_KEY_STYLE = 'path';
20
+ const VALID_FLATTEN_KEY_STYLES = new Set(['path', 'leaf']);
19
21
 
20
22
  /**
21
23
  * Returns true if the segment contains a mix of snake_case (underscore) and
@@ -161,6 +163,35 @@ function flattenObject(obj, prefix = '', out = {}) {
161
163
  return out;
162
164
  }
163
165
 
166
+ /**
167
+ * Recursively flattens a nested plain object using only each leaf property
168
+ * name as the output key.
169
+ *
170
+ * Example: `{ a: { b: { c: 1, d: 2 } } }` => `{ c: 1, d: 2 }`.
171
+ * If duplicate leaf keys exist at different nesting levels, the higher-level
172
+ * leaf wins. If duplicate leaf keys exist at the same depth, the first
173
+ * traversal wins.
174
+ *
175
+ * @param {Object} obj
176
+ * @param {Object} [out]
177
+ * @param {Object} [depthByKey]
178
+ * @param {number} [depth]
179
+ * @returns {Object}
180
+ */
181
+ function flattenObjectByLeafKey(obj, out = {}, depthByKey = {}, depth = 1) {
182
+ for (const [key, value] of Object.entries(obj)) {
183
+ if (value !== null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)) {
184
+ flattenObjectByLeafKey(value, out, depthByKey, depth + 1);
185
+ } else {
186
+ if (!Object.prototype.hasOwnProperty.call(out, key) || depth < depthByKey[key]) {
187
+ out[key] = value;
188
+ depthByKey[key] = depth;
189
+ }
190
+ }
191
+ }
192
+ return out;
193
+ }
194
+
164
195
  /**
165
196
  * Validates and transforms whitelisted properties from an input object.
166
197
  *
@@ -181,8 +212,11 @@ function flattenObject(obj, prefix = '', out = {}) {
181
212
  * element passes validation, and the returned value is an array of the
182
213
  * validated elements (in the same order).
183
214
  * 5. Optionally converts all keys (including nested) to snake_case.
184
- * 6. Optionally flattens the result so every leaf is a top-level key,
185
- * joined by `.` (`flattenOutput`). Applied after snake_case conversion.
215
+ * 6. Optionally flattens the result (`flattenOutput`). Flattened keys use
216
+ * full dot paths by default (`flattenKeyStyle: 'path'`) or direct leaf
217
+ * names when requested (`flattenKeyStyle: 'leaf'`). For duplicate leaf
218
+ * keys in leaf mode, the shallower value wins; ties keep the first value
219
+ * encountered. Applied after snake_case conversion.
186
220
  *
187
221
  * @param {Object} inputObject - The input object (e.g., req.body / req.params).
188
222
  * @param {Array<string>} [requiredProperties=[]] - Leaf paths that MUST be present and valid.
@@ -190,17 +224,26 @@ function flattenObject(obj, prefix = '', out = {}) {
190
224
  * @param {Array<string>} [options.optionalProperties=[]] - Leaf paths allowed but not required.
191
225
  * @param {boolean} [options.convertToSnakeCase=false] - Whether to convert keys to snake_case.
192
226
  * @param {boolean} [options.flattenOutput=false] - Whether to flatten the result so that
193
- * every leaf is a top-level key (joined by `.`), with no nested objects in the output.
227
+ * every leaf is a top-level key, with no nested objects in the output.
228
+ * @param {'path'|'leaf'} [options.flattenKeyStyle='path'] - Flattened key naming strategy
229
+ * when `flattenOutput` is true. `path` uses dot-joined paths; `leaf` uses leaf names.
194
230
  * @returns {Promise<Object>} Resolves with the validated (and possibly transformed) object.
195
231
  */
196
232
  function validateWhitelistProperties(
197
233
  inputObject,
198
234
  requiredProperties = [],
199
- options = { optionalProperties: [], convertToSnakeCase: false, flattenOutput: false },
235
+ options = { optionalProperties: [], convertToSnakeCase: false, flattenOutput: false, flattenKeyStyle: DEFAULT_FLATTEN_KEY_STYLE },
200
236
  ) {
201
237
  const optionalProperties = (options && options.optionalProperties) || [];
202
238
  const convertToSnakeCase = !!(options && options.convertToSnakeCase);
203
239
  const flattenOutput = !!(options && options.flattenOutput);
240
+ const flattenKeyStyle = options && options.flattenKeyStyle !== undefined ? options.flattenKeyStyle : DEFAULT_FLATTEN_KEY_STYLE;
241
+
242
+ if (!VALID_FLATTEN_KEY_STYLES.has(flattenKeyStyle)) {
243
+ throwBadInputError({
244
+ userMessage: `Invalid flattenKeyStyle: ${String(flattenKeyStyle)}. Expected "path" or "leaf"`,
245
+ });
246
+ }
204
247
 
205
248
  // Cap the total number of paths to validate per call.
206
249
  const totalKeys = (requiredProperties ? requiredProperties.length : 0) + optionalProperties.length;
@@ -271,9 +314,9 @@ function validateWhitelistProperties(
271
314
  validatedObject = keysToSnakeCase(validatedObject);
272
315
  }
273
316
 
274
- // 6. Optional flattening: produce a flat object with dot-joined keys.
317
+ // 6. Optional flattening.
275
318
  if (flattenOutput) {
276
- validatedObject = flattenObject(validatedObject);
319
+ validatedObject = flattenKeyStyle === 'leaf' ? flattenObjectByLeafKey(validatedObject) : flattenObject(validatedObject);
277
320
  }
278
321
 
279
322
  return Promise.resolve(validatedObject);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carecard/validate",
3
- "version": "3.1.26",
3
+ "version": "3.2.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/CareCard-ca/pkg-validate.git"
package/readme.md CHANGED
@@ -65,6 +65,7 @@ a string on failure and `null` on success.
65
65
  | `isValidDomainName(value)` | Domain name with at least one dot, valid DNS-like labels, and max total length 253. |
66
66
  | `isValidTimestampzString(value)` | ISO 8601 timestamp with `Z` or `+/-HH:MM` timezone offset. |
67
67
  | `isValidTimestampString(value)` | ISO 8601 timestamp without timezone offset. |
68
+ | `isValidDateString(value)` | ISO date in `YYYY-MM-DD` format. |
68
69
  | `isValidUrl(value)` | Absolute `http://` or `https://` URL up to 2048 chars. |
69
70
  | `isValidArrayOfStrings(value)` | Array where every element passes `isSafeString`. |
70
71
 
@@ -95,27 +96,29 @@ validateProperties(input);
95
96
  Keys are matched exactly. Both snake_case and camelCase variants are listed
96
97
  where the package supports both.
97
98
 
98
- | Validator | Keys |
99
- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
100
- | `isNameString` | `first_name`, `firstName`, `last_name`, `lastName`, `username`, `new_status`, `newStatus`, `description`, `comment`, `status`, `name`, `title`, `brand`, `short_description`, `shortDescription`, `college_name`, `collegeName`, `campus_name`, `campusName`, `role`, `role_id`, `roleId`, `campus`, `institution_name`, `institutionName`, `program_name`, `programName`, `role_name`, `roleName`, `document_type`, `documentType`, `reason`, `entity_type`, `entityType`, `action_type`, `actionType`, `city`, `state`, `country`, `type` |
101
- | `isStreetString` | `street` |
102
- | `isCharactersString` | `postal_code`, `postalCode`, `period` |
103
- | `isBoolValue` | `is_primary`, `isPrimary`, `active` |
104
- | `isSafeSearchString` | `search_string`, `searchString` |
105
- | `isString6To16CharacterLong` and `isSimplePasswordString` | `password`, `new_password`, `newPassword` |
106
- | `isString6To16CharacterLong` and `isPasswordString` | `strong_password`, `strongPassword` |
107
- | `isEmailString` | `email` |
108
- | `isPhoneNumber` | `phone_number`, `phoneNumber` |
109
- | `isCountryCodeString` | `country_code`, `countryCode` |
110
- | `isUrlSafeString` | `token`, `email_confirm_token`, `emailConfirmToken`, `verification_token`, `verificationToken` |
111
- | `isValidUuidString` | `uuid`, `item_id`, `itemId`, `user_id`, `userId`, `address_id`, `addressId`, `image_id`, `imageId`, `order_id`, `orderId`, `category_id`, `categoryId`, `parent_id`, `parentId`, `college_id`, `collegeId`, `campus_id`, `campusId`, `program_id`, `programId`, `id`, `institution_id`, `institutionId`, `role_assignment_id`, `roleAssignmentId`, `user_role_id`, `userRoleId`, `phone_number_id`, `phoneNumberId`, `entity_id`, `entityId`, `changed_by`, `changedBy`, `request_id`, `requestId` |
112
- | `isValidIntegerString` | `offset_number`, `offsetNumber`, `number_of_orders`, `numberOfOrders`, `price`, `from`, `number`, `limit`, `offset` |
113
- | `isValidJsonString` on the raw value | `about` |
114
- | `isValidJsonString(JSON.stringify(value))` | `weight`, `dimensions`, `permission`, `scope_data`, `scopeData`, `meta_data`, `metaData` |
115
- | `isValidArrayOfStrings` | `aliases` |
116
- | `isImageUrl` or `isValidUrl` | `image_url`, `imageUrl`, `website`, `file_url`, `fileUrl` |
117
- | `isValidDomainName` | `domain_name`, `domainName`, `domain`, `email_domain`, `emailDomain`, `email_domain_name`, `emailDomainName` |
118
- | `isValidTimestampzString` or `isValidTimestampString` | `expires_at`, `expiresAt`, `start_time`, `startTime`, `end_time`, `endTime` |
99
+ | Validator | Keys |
100
+ | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
101
+ | `isNameString` | `first_name`, `firstName`, `last_name`, `lastName`, `username`, `new_status`, `newStatus`, `description`, `comment`, `status`, `name`, `title`, `brand`, `short_description`, `shortDescription`, `college_name`, `collegeName`, `campus_name`, `campusName`, `role`, `role_id`, `roleId`, `campus`, `institution_name`, `institutionName`, `program_name`, `programName`, `role_name`, `roleName`, `document_name`, `documentName`, `document_required_for_role_name`, `documentRequiredForRoleName`, `reason`, `entity_type`, `entityType`, `action_type`, `actionType`, `city`, `state`, `country`, `type` |
102
+ | `isStreetString` | `street` |
103
+ | `isCharactersString` | `postal_code`, `postalCode`, `period` |
104
+ | `isBoolValue` | `is_primary`, `isPrimary`, `active`, `document_optional`, `documentOptional` |
105
+ | `isSafeSearchString` | `search_string`, `searchString` |
106
+ | `isString6To16CharacterLong` and `isSimplePasswordString` | `password`, `new_password`, `newPassword` |
107
+ | `isString6To16CharacterLong` and `isPasswordString` | `strong_password`, `strongPassword` |
108
+ | `isEmailString` | `email` |
109
+ | `isPhoneNumber` | `phone_number`, `phoneNumber` |
110
+ | `isCountryCodeString` | `country_code`, `countryCode` |
111
+ | `isUrlSafeString` | `token`, `email_confirm_token`, `emailConfirmToken`, `verification_token`, `verificationToken` |
112
+ | `isValidUuidString` | `uuid`, `item_id`, `itemId`, `user_id`, `userId`, `address_id`, `addressId`, `image_id`, `imageId`, `order_id`, `orderId`, `category_id`, `categoryId`, `parent_id`, `parentId`, `college_id`, `collegeId`, `campus_id`, `campusId`, `program_id`, `programId`, `program_requirement_document_id`, `programRequirementDocumentId`, `program_document_id`, `programDocumentId`, `id`, `institution_id`, `institutionId`, `role_assignment_id`, `roleAssignmentId`, `user_role_id`, `userRoleId`, `phone_number_id`, `phoneNumberId`, `entity_id`, `entityId`, `changed_by`, `changedBy`, `request_id`, `requestId` |
113
+ | `isValidIntegerString` | `offset_number`, `offsetNumber`, `number_of_orders`, `numberOfOrders`, `price`, `from`, `number`, `limit`, `offset` |
114
+ | `isValidJsonString` on the raw value | `about` |
115
+ | `isValidJsonString(JSON.stringify(value))` | `weight`, `dimensions`, `permission`, `scope_data`, `scopeData`, `meta_data`, `metaData` |
116
+ | `isTextString` | `document_description`, `documentDescription`, `nick_name`, `nickName`, `requested_by_name`, `requestedByName`, `requested_by_email`, `requestedByEmail`, `requested_by_phone`, `requestedByPhone`, `approved_by_name`, `approvedByName`, `approved_by_email`, `approvedByEmail`, `approved_by_phone`, `approvedByPhone` |
117
+ | `isValidArrayOfStrings` | `aliases` |
118
+ | `isImageUrl` or `isValidUrl` | `image_url`, `imageUrl`, `website`, `file_url`, `fileUrl` |
119
+ | `isValidDomainName` | `domain_name`, `domainName`, `domain`, `email_domain`, `emailDomain`, `email_domain_name`, `emailDomainName` |
120
+ | `isValidTimestampzString` or `isValidTimestampString` | `expires_at`, `expiresAt`, `start_time`, `startTime`, `end_time`, `endTime` |
121
+ | `isValidDateString` | `document_required_by_date`, `documentRequiredByDate`, `effective_start_date`, `effectiveStartDate`, `effective_end_date`, `effectiveEndDate`, `valid_until_date`, `validUntilDate`, `renew_date`, `renewDate` |
119
122
 
120
123
  ## `validateWhitelistProperties(inputObject, requiredProperties, options)`
121
124