diff --git a/spec-driven/README.md b/spec-driven/README.md new file mode 100644 index 00000000..27cd98d6 --- /dev/null +++ b/spec-driven/README.md @@ -0,0 +1,48 @@ +# spec-driven/ + +This directory contains specification-driven development artifacts for ALKS CLI features. + +## Structure + +Each feature has its own subdirectory under `spec-driven/`: + +``` +spec-driven/ +├── / +│ ├── spec.md # Feature specification +│ ├── plan.md # Implementation plan +│ └── tasks.md # Task breakdown (generated from plan) +├── .sessions/ # Session state for resumable workflows +└── README.md # This file +``` + +## Workflow + +1. **Specification** (`/spec`) - Define WHAT to build + - Created from Rally features, PRDs, or user input + - Contains functional requirements, acceptance criteria, NFRs + +2. **Planning** (`/plan`) - Define HOW to build it + - Breaks down spec into implementation steps + - Includes architecture decisions, dependencies, verification criteria + +3. **Task Decomposition** (`/task`) - Generate executable work items + - Transforms plan steps into fine-grained tasks + - Supports parallel execution and progress tracking + +4. **Execution** (`/execute`) - Implement the changes + - Executes tasks with automated verification + - Creates traceable commits + +## Current Features + +- **changeminder-output-formats** - Add ChangeMinder change number to all ALKS CLI output formats (Rally F281658) + +## Usage + +Use the spec-driven-skills to work with these artifacts: +- `/spec-driven-skills:spec` - Create a specification +- `/spec-driven-skills:plan` - Generate an implementation plan +- `/spec-driven-skills:task` - Decompose plan into tasks +- `/spec-driven-skills:execute` - Execute the tasks +- `/spec-driven-skills:verify` - Verify implementation against spec diff --git a/spec-driven/changeminder-output-formats/plan.md b/spec-driven/changeminder-output-formats/plan.md new file mode 100644 index 00000000..f25cf7c1 --- /dev/null +++ b/spec-driven/changeminder-output-formats/plan.md @@ -0,0 +1,298 @@ +# Implementation Plan: Add ChangeMinder Change Number to All Output Formats + +**Feature:** Add ChangeMinder Change Number to All Output Formats +**spec_source:** spec-driven/changeminder-output-formats/spec.md +**spec_hash:** a1d08bea375f8daabbc61ece3714c7bd2c92a0a7f67660f927ed4744546eb8aa +**version:** 1.0 +**status:** final +**created:** 2026-03-23 + +--- + +## Overview + +When users run `alks sessions open` with ChangeMinder flags (`--ciid`, `--activity-type`, `--description`), six output formats silently discard the change ticket number, leaving users unable to reference it. This plan implements changeNumber output for all missing formats: `creds`, `docker`, `terraformarg`, `terraformenv`, `aws`, and `idea`. + +### Component Mapping + +**Primary Component:** Output Formatting (`src/lib/getKeyOutput.ts`, `src/lib/updateCreds.ts`) +- All six FRs extend the existing switch-based output formatter +- FR-1 additionally modifies the credentials file writer to persist changeNumber in `~/.aws/credentials` + +**Affected Files:** +- `src/lib/getKeyOutput.ts` - Main output formatter (all 6 FRs) +- `src/lib/updateCreds.ts` - Credentials file writer (FR-1 only) +- Test files to be created/modified per format + +**Existing Pattern:** Five formats already output changeNumber (json, powershell, fishshell, linux, export/set). We follow the same conditional output pattern: only include changeNumber when it's defined. + +### Test Strategy + +- **Approach:** tdd +- **Framework:** jest +- **Pattern Reference:** Follow existing test patterns from `src/lib/getKeyOutput.test.ts` (if exists) or similar output formatting tests +- **Files Needing Tests:** + - `src/lib/getKeyOutput.ts` (all 6 formats) + - `src/lib/updateCreds.ts` (creds format only) + +**Test Coverage Requirements (NFR-1):** +- Each format must have tests covering: + - changeNumber present when provided (AC-*.1) + - Output unchanged when changeNumber absent (AC-*.2) + +--- + +## Traceability + +| STEP | FR | AC | Files Modified | Effort | +|------|----|----|---------------|--------| +| STEP-1 | FR-1 | AC-1.1, AC-1.2 | getKeyOutput.ts, getKeyOutput.test.ts | M | +| STEP-2 | FR-1 | AC-1.1 | updateCreds.ts, updateCreds.test.ts | M | +| STEP-3 | FR-2 | AC-2.1, AC-2.2 | getKeyOutput.ts, getKeyOutput.test.ts | S | +| STEP-4 | FR-3 | AC-3.1, AC-3.2 | getKeyOutput.ts, getKeyOutput.test.ts | S | +| STEP-5 | FR-4 | AC-4.1, AC-4.2 | getKeyOutput.ts, getKeyOutput.test.ts | S | +| STEP-6 | FR-5 | AC-5.1, AC-5.2 | getKeyOutput.ts, getKeyOutput.test.ts | S | +| STEP-7 | FR-6 | AC-6.1, AC-6.2 | getKeyOutput.ts, getKeyOutput.test.ts | S | + +--- + +## Phase 1: Walking Skeleton - Prove creds Format Pattern + +**Goal:** Prove the changeNumber output pattern end-to-end with the most complex format (`creds`), which touches both output formatting and file writing. + +### STEP-1: Implement changeNumber in creds output format +[FR-1 → AC-1.1, AC-1.2] | modify src/lib/getKeyOutput.ts | Effort: M + +> **Intent:** The creds format returns empty string (no console output) since credentials are written to file by updateCreds.ts. However, getKeyOutput must still construct the Key object with changeNumber populated so downstream consumers (including updateCreds.ts) receive it. Missing this will cause updateCreds to receive undefined changeNumber even when the ticket was created. + +**Implementation:** +- Write failing tests in `src/lib/getKeyOutput.test.ts` or create if missing: + - When format is `creds` and changeNumber is defined, verify Key object includes changeNumber (AC-1.1) + - When format is `creds` and changeNumber is undefined, verify backward compatibility (AC-1.2) +- Modify `src/lib/getKeyOutput.ts` in the `creds` case: + - Ensure the returned value or data structure includes changeNumber when key.changeNumber is defined + - Follow existing pattern from `json` format which already outputs changeNumber +- Reference existing changeNumber handling in json/powershell formats for conditional output pattern + +**Dependencies:** None +**Enables:** STEP-2 + +**Verify:** +- `npm test -- --testPathPattern=getKeyOutput` +- `tsc --noEmit` (type check) + +--- + +### STEP-2: Write changeNumber to AWS credentials file +[FR-1 → AC-1.1] | modify src/lib/updateCreds.ts | Effort: M + +> **Intent:** AWS credentials files use INI format with `[profile-name]` sections. changeNumber must be written as a comment line (e.g., `# ALKS_CHANGE_NUMBER=CHG123456`) immediately above the profile section to preserve INI spec compliance. Writing it as a key-value pair within the section will cause AWS CLI to fail parsing unrecognized fields. + +**Implementation:** +- Write failing tests in `src/lib/updateCreds.test.ts` or create if missing: + - When changeNumber is present, verify it's written as a comment above the profile section + - When changeNumber is absent, verify credentials file format unchanged + - Verify comment format: `# ALKS_CHANGE_NUMBER=` +- Modify `src/lib/updateCreds.ts`: + - Before writing profile section via `propIni.addData`, insert comment line with changeNumber + - Only add comment when `key.changeNumber` is defined + - Follow INI comment syntax: lines starting with `#` +- Reference `src/lib/awsCredentialsFileContstants.ts` for field name constants + +**Dependencies:** STEP-1 (proves changeNumber is available in Key object) + +**Verify:** +- `npm test -- --testPathPattern=updateCreds` +- Integration test: Run `alks sessions open --ciid 123 -o creds` and inspect `~/.aws/credentials` for comment line +- `tsc --noEmit` + +--- + +## Phase 2: Incremental Depth - Remaining Formats + +**Goal:** Extend changeNumber output to the remaining five formats, following the pattern proven in Phase 1. + +**Parallelization Note:** STEP-3 through STEP-7 are independent and can execute in parallel. + +### STEP-3: Implement changeNumber in docker output format +[FR-2 → AC-2.1, AC-2.2] | modify src/lib/getKeyOutput.ts | Effort: S | **Parallel** + +> **Intent:** Docker format outputs environment variables as `-e KEY=value` arguments for `docker run`. changeNumber must use the exact variable name `ALKS_CHANGE_NUMBER` (not `CHANGE_NUMBER`) to maintain consistency with existing ALKS-prefixed variables in terraform formats and avoid collision with user-defined container environment variables. + +**Implementation:** +- Write failing tests in `src/lib/getKeyOutput.test.ts`: + - When format is `docker` and changeNumber is defined, verify output includes `-e ALKS_CHANGE_NUMBER=` (AC-2.1) + - When format is `docker` and changeNumber is undefined, verify output unchanged (AC-2.2) +- Modify `src/lib/getKeyOutput.ts` in the `docker` case: + - Append `-e ALKS_CHANGE_NUMBER=${key.changeNumber}` to output string when key.changeNumber is defined + - Follow existing pattern for AWS credential variables +- Reference existing docker format output structure for spacing/formatting + +**Dependencies:** None (independent of other Phase 2 steps) +**Enables (conceptual):** STEP-4, STEP-5, STEP-6, STEP-7 (proves pattern for env-style formats) + +**Verify:** +- `npm test -- --testPathPattern=getKeyOutput` +- `tsc --noEmit` + +--- + +### STEP-4: Implement changeNumber in terraformarg output format +[FR-3 → AC-3.1, AC-3.2] | modify src/lib/getKeyOutput.ts | Effort: S | **Parallel** + +> **Intent:** Terraform argument format outputs as `-var key=value` for CLI usage. The variable name `alks_change_number` uses snake_case (not camelCase or CONSTANT_CASE) to match Terraform variable naming conventions. Using CONSTANT_CASE would cause Terraform to reject the variable as invalid syntax. + +**Implementation:** +- Write failing tests in `src/lib/getKeyOutput.test.ts`: + - When format is `terraformarg` and changeNumber is defined, verify output includes `-var alks_change_number=` (AC-3.1) + - When format is `terraformarg` and changeNumber is undefined, verify output unchanged (AC-3.2) +- Modify `src/lib/getKeyOutput.ts` in the `terraformarg` case: + - Append `-var alks_change_number=${key.changeNumber}` to output string when key.changeNumber is defined + - Follow existing pattern for AWS credential variables in terraformarg format +- Reference existing terraformarg variable naming (snake_case with alks_ prefix) + +**Dependencies:** None (independent of other Phase 2 steps) + +**Verify:** +- `npm test -- --testPathPattern=getKeyOutput` +- `tsc --noEmit` + +--- + +### STEP-5: Implement changeNumber in terraformenv output format +[FR-4 → AC-4.1, AC-4.2] | modify src/lib/getKeyOutput.ts | Effort: S | **Parallel** + +> **Intent:** Terraform environment variable format outputs as `export KEY=value` for shell sourcing. The variable name `ALKS_CHANGE_NUMBER` uses CONSTANT_CASE (not snake_case) to match shell environment variable conventions and distinguish from Terraform's -var arguments. Shell variables are case-sensitive — mixing cases will create separate variables. + +**Implementation:** +- Write failing tests in `src/lib/getKeyOutput.test.ts`: + - When format is `terraformenv` and changeNumber is defined, verify output includes `export ALKS_CHANGE_NUMBER=` (AC-4.1) + - When format is `terraformenv` and changeNumber is undefined, verify output unchanged (AC-4.2) +- Modify `src/lib/getKeyOutput.ts` in the `terraformenv` case: + - Append `export ALKS_CHANGE_NUMBER=${key.changeNumber}` to output string when key.changeNumber is defined + - Follow existing pattern for AWS credential variables in terraformenv format +- Reference `src/lib/isWindows.ts` for platform-specific command syntax if needed + +**Dependencies:** None (independent of other Phase 2 steps) + +**Verify:** +- `npm test -- --testPathPattern=getKeyOutput` +- `tsc --noEmit` + +--- + +### STEP-6: Implement changeNumber in aws output format +[FR-5 → AC-5.1, AC-5.2] | modify src/lib/getKeyOutput.ts | Effort: S | **Parallel** + +> **Intent:** AWS credential process format outputs JSON per AWS CLI's external credential provider spec (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sourcing-external.html). changeNumber must be added as a top-level JSON property, not nested under another key. The property name should use camelCase (`changeNumber`) to match existing JSON keys like `sessionToken`, not snake_case or CONSTANT_CASE which would break naming consistency in the JSON output. + +**Implementation:** +- Write failing tests in `src/lib/getKeyOutput.test.ts`: + - When format is `aws` and changeNumber is defined, verify JSON output includes `"changeNumber": ""` (AC-5.1) + - When format is `aws` and changeNumber is undefined, verify JSON output unchanged (AC-5.2) +- Modify `src/lib/getKeyOutput.ts` in the `aws` case: + - Add changeNumber property to JSON object when key.changeNumber is defined + - Follow existing JSON structure (top-level property, camelCase naming) +- Reference existing aws format JSON structure + +**Dependencies:** None (independent of other Phase 2 steps) + +**Verify:** +- `npm test -- --testPathPattern=getKeyOutput` +- `tsc --noEmit` + +--- + +### STEP-7: Implement changeNumber in idea output format +[FR-6 → AC-6.1, AC-6.2] | modify src/lib/getKeyOutput.ts | Effort: S | **Parallel** + +> **Intent:** IntelliJ IDEA environment variable format outputs XML elements for run configuration import. changeNumber must be added as a new `` element with name="ALKS_CHANGE_NUMBER", not as a text node or attribute on existing elements. Incorrect XML structure will cause IDEA to silently ignore the variable or fail to parse the run configuration entirely. + +**Implementation:** +- Write failing tests in `src/lib/getKeyOutput.test.ts`: + - When format is `idea` and changeNumber is defined, verify output includes ALKS_CHANGE_NUMBER in IDEA environment variable format (AC-6.1) + - When format is `idea` and changeNumber is undefined, verify output unchanged (AC-6.2) +- Modify `src/lib/getKeyOutput.ts` in the `idea` case: + - Add changeNumber to output in IDEA format when key.changeNumber is defined + - Follow existing pattern for AWS credential variables in idea format +- Reference existing idea format structure (likely XML or special env var format) + +**Dependencies:** None (independent of other Phase 2 steps) + +**Verify:** +- `npm test -- --testPathPattern=getKeyOutput` +- `tsc --noEmit` + +--- + +## Architecture Decisions + +*No architecture decisions required. This feature extends existing output formatting logic following established patterns.* + +--- + +## Risks + +| Risk | Severity | Mitigation | +|------|----------|-----------| +| AWS credentials file corruption if changeNumber comment breaks INI parser | Medium | Test with real AWS CLI after updateCreds changes. Use standard INI comment syntax (`#`) recognized by all parsers. Add integration test that validates credentials file can still be parsed. | +| Platform-specific differences in output format (Windows vs Unix) | Low | Follow existing platform detection pattern in `src/lib/isWindows.ts`. Test on both platforms if possible. | +| Breaking change if user scripts parse output and don't expect changeNumber field | Low | Spec constraint: only output changeNumber when ChangeMinder flags provided. Existing usage without flags remains unchanged (AC-*.2 validates this). | + +--- + +## Non-Functional Verification + +| NFR | Target | Mapped STEPs | Verification Method | +|-----|--------|--------------|-------------------| +| NFR-1 | Unit tests for all modified output formats | STEP-1 through STEP-7 | Each STEP includes test bullets covering changeNumber presence/absence. Verify via `npm test -- --coverage` shows >80% coverage for modified functions. | +| NFR-2 | Follow TypeScript/Prettier/TSLint conventions | STEP-1 through STEP-7 | `tsc --noEmit` passes (type safety). `npm run prettier -- --check` passes. `npm run tslint` passes. Pre-commit hooks enforce on commit. | +| NFR-3 | 100% backward compatibility when no ChangeMinder flags | STEP-1 through STEP-7 | Each AC-*.2 validates output unchanged when changeNumber undefined. Integration test: `alks sessions open -o ` (without --ciid) produces identical output to baseline. | +| NFR-4 | No performance impact | STEP-1 through STEP-7 | Adding a conditional string append or object property has negligible overhead. No verification beyond code review (O(1) operation). | +| NFR-5 | changeNumber not sensitive data | STEP-1 through STEP-7 | Review: changeNumber output follows same security posture as existing credential output (displayed in terminal, written to files). No special handling required. | + +--- + +## Open Questions + +*None at this time.* + +--- + +## Dependency Graph + +```mermaid +graph TD + STEP1[STEP-1: Implement changeNumber in creds output format] + STEP2[STEP-2: Write changeNumber to AWS credentials file] + STEP3[STEP-3: Implement changeNumber in docker format] + STEP4[STEP-4: Implement changeNumber in terraformarg format] + STEP5[STEP-5: Implement changeNumber in terraformenv format] + STEP6[STEP-6: Implement changeNumber in aws format] + STEP7[STEP-7: Implement changeNumber in idea format] + + STEP1 --> STEP2 + STEP3 + STEP4 + STEP5 + STEP6 + STEP7 + + style STEP3 fill:#e1f5ff + style STEP4 fill:#e1f5ff + style STEP5 fill:#e1f5ff + style STEP6 fill:#e1f5ff + style STEP7 fill:#e1f5ff +``` + +*Note: STEPs with blue fill (STEP-3 through STEP-7) can execute in parallel.* + +--- + +## Notes + +- **Primary Use Case:** Santiago Sandoval's DLT team using `-o creds` for scripted DLQ replay workflows +- **Existing Implementations:** Five formats already output changeNumber (json, powershell, fishshell, linux, export/set) — reference these for conditional output pattern +- **Pattern Consistency:** Use ALKS_ prefix for environment variables (docker, terraformenv) to avoid namespace collision +- **Terraform Naming:** terraformarg uses snake_case (`alks_change_number`), terraformenv uses CONSTANT_CASE (`ALKS_CHANGE_NUMBER`) per their respective conventions +- **Post-Implementation:** Notify Santiago Sandoval and Nick Gibson via #alks-support Slack thread (Success Metric 3) diff --git a/spec-driven/changeminder-output-formats/progress.md b/spec-driven/changeminder-output-formats/progress.md new file mode 100644 index 00000000..5cea559c --- /dev/null +++ b/spec-driven/changeminder-output-formats/progress.md @@ -0,0 +1,85 @@ +# Progress Tracker: Add ChangeMinder Change Number to All Output Formats + +**Last Updated:** 2026-03-23 +**Status:** Ready to Execute + +--- + +## Current State + +**Phase:** Complete +**Active Bundle:** — +**Last Completed:** TASK-7 +**Baseline:** 086b0b1 +**Baseline Exit Code:** 1 (pre-existing test failures - module resolution issue) + +--- + +## Task Status + +| Task ID | Title | Status | Commit | +|---------|-------|--------|--------| +| TASK-1 | Implement changeNumber in creds output format | done | 3d6c4bc | +| TASK-2 | Write changeNumber to AWS credentials file | done | e282107 | +| TASK-3 | Implement changeNumber in docker output format | done | 9b0b3f2 | +| TASK-4 | Implement changeNumber in terraformarg output format | done | bdd81c4 | +| TASK-5 | Implement changeNumber in terraformenv output format | done | d8c5d92 | +| TASK-6 | Implement changeNumber in aws output format | done | a4c4e43 | +| TASK-7 | Implement changeNumber in idea output format | done | a4c4e43 | + +--- + +## Bundle Progress + +### Bundle 1: Walking Skeleton - Prove creds Format Pattern +**Status:** complete +**Tasks:** 2/2 complete + +- [x] TASK-1: Implement changeNumber in creds output format (3d6c4bc) +- [x] TASK-2: Write changeNumber to AWS credentials file (e282107) + +### Bundle 2: Incremental Depth - Remaining Formats [P] +**Status:** complete +**Tasks:** 5/5 complete + +- [x] TASK-3: Implement changeNumber in docker output format (9b0b3f2) +- [x] TASK-4: Implement changeNumber in terraformarg output format (bdd81c4) +- [x] TASK-5: Implement changeNumber in terraformenv output format (d8c5d92) +- [x] TASK-6: Implement changeNumber in aws output format (a4c4e43) +- [x] TASK-7: Implement changeNumber in idea output format (a4c4e43) + +--- + +## Execution Notes + +This file is automatically updated by the execute skill. Do not edit manually. + +**Sequential Mode:** Execute bundles in order (Bundle 1, then Bundle 2) +**Agent Mode:** Bundle 2 tasks can run in parallel (switch case isolation) +**Team Mode:** Distribute Bundle 2 tasks across team members + +--- + +## Session Log + +### 2026-03-23 — Bundle 1: Walking Skeleton - Prove creds Format Pattern +- Completed: TASK-1: Implement changeNumber in creds output format, TASK-2: Write changeNumber to AWS credentials file +- Decisions: none +- Next: TASK-3: Implement changeNumber in docker output format + +### 2026-03-23 — Bundle 2: Incremental Depth - Remaining Formats +- Completed: TASK-3: docker format, TASK-4: terraformarg format, TASK-5: terraformenv format, TASK-6: aws format, TASK-7: idea format +- Decisions: TASK-6 and TASK-7 implemented together (same commit a4c4e43) +- Next: Verification complete - all tasks done + +--- + +## Completion Criteria + +- [x] Spec created (spec.md) +- [x] Plan created (plan.md) +- [x] Tasks decomposed (tasks.md) +- [ ] All 7 tasks completed +- [ ] All tests passing (`npm test`) +- [ ] Type checking clean (`tsc --noEmit`) +- [ ] Santiago Sandoval and Nick Gibson notified via #alks-support diff --git a/spec-driven/changeminder-output-formats/spec.md b/spec-driven/changeminder-output-formats/spec.md new file mode 100644 index 00000000..f72d7193 --- /dev/null +++ b/spec-driven/changeminder-output-formats/spec.md @@ -0,0 +1,218 @@ +# Specification: Add ChangeMinder Change Number to All Output Formats + +**Feature ID:** F281658 +**Version:** 1.0 +**Status:** Draft +**Last Updated:** 2026-03-23 + +--- + +## Overview + +When users run `alks sessions open` with ChangeMinder flags (`--ciid`, `--activity-type`, `--description`), a change ticket is successfully created in ChangeMinder, but six output formats (`creds`, `docker`, `terraformarg`, `terraformenv`, `aws`, `idea`) silently discard the ticket number, leaving users with no way to reference it. This is problematic for scripted workflows (like DLT's DLQ replay) that need to capture the change ticket number. + +**Core Principle:** If a user requests a ticket, we tell them the ticket number—regardless of output format. + +### Current State + +- **Working Formats** (already include `changeNumber`): `json`, `env`, `powershell`, `linux`, `fishshell` +- **Missing Formats** (silently discard `changeNumber`): `creds`, `docker`, `terraformarg`, `terraformenv`, `aws`, `idea` +- The change ticket is created successfully regardless of output format, but the ticket number is lost for the six missing formats +- Implementation files: + - `src/lib/getKeyOutput.ts` - Main output formatting logic + - `src/lib/updateCreds.ts` - AWS credentials file writing (for `creds` format) + +### Business Context + +This issue was reported by Santiago Sandoval (DLT team) and independently confirmed by Nick Gibson via #alks-support thread. Santiago's team relies on `-o creds` for scripted DLQ replay workflows and needs the change ticket number written to the credentials file for audit and tracking purposes. The same gap applies to all other formats used across Cox Automotive engineering teams. + +--- + +## Goals + +### Primary Goal +Ensure all output formats include `changeNumber` when ChangeMinder flags are provided and a ticket is successfully created. + +### Secondary Goals +1. Provide consistent user experience across all output formats when ChangeMinder integration is used +2. Enable scripted workflows to capture and use the change ticket number for compliance and audit trails + +--- + +## Users + +### Primary Users +- **DLT Team (Santiago Sandoval)** - DevOps engineers running scripted DLQ replay workflows using `-o creds` format, need change ticket numbers for compliance tracking +- **Nick Gibson's Team** - Platform engineers requiring ChangeMinder integration for infrastructure changes +- **ALKS CLI Users (General)** - Any Cox Automotive engineer using ChangeMinder with non-JSON output formats for AWS credential management + +--- + +## Functional Requirements + +### FR-1: Output changeNumber in `creds` format +**Priority:** Must Have +**Assigned to Goal:** Primary Goal + +When ChangeMinder flags (`--ciid`, `--activity-type`, `--description`) are provided and a change ticket is successfully created, include `changeNumber` in the AWS credentials file output. + +**Implementation Areas:** +- `src/lib/getKeyOutput.ts` - Add changeNumber to creds output generation +- `src/lib/updateCreds.ts` - Write changeNumber to `.aws/credentials` file + +**Acceptance Criteria:** +- **AC-1.1:** Given a user runs `alks sessions open --ciid 123 --activity-type "Deploy" --description "DLQ replay" -o creds`, When a ChangeMinder ticket is successfully created, Then the changeNumber is written to the `.aws/credentials` file as a comment or metadata field +- **AC-1.2:** Given a user runs `alks sessions open -o creds` without ChangeMinder flags, When credentials are generated, Then the output remains unchanged from current behavior (backward compatibility) + +--- + +### FR-2: Output changeNumber in `docker` format +**Priority:** Must Have +**Assigned to Goal:** Primary Goal + +When ChangeMinder flags are provided and a change ticket is successfully created, include `changeNumber` in Docker environment argument format. + +**Implementation Areas:** +- `src/lib/getKeyOutput.ts` - Add changeNumber to docker output generation + +**Acceptance Criteria:** +- **AC-2.1:** Given a user runs `alks sessions open --ciid 123 -o docker`, When a ChangeMinder ticket is successfully created, Then the changeNumber is included in Docker `-e` environment argument format (e.g., `-e ALKS_CHANGE_NUMBER=CHG123456`) +- **AC-2.2:** Given a user runs `alks sessions open -o docker` without ChangeMinder flags, When credentials are generated, Then the output remains unchanged from current behavior + +--- + +### FR-3: Output changeNumber in `terraformarg` format +**Priority:** Must Have +**Assigned to Goal:** Primary Goal + +When ChangeMinder flags are provided and a change ticket is successfully created, include `changeNumber` in Terraform argument format with `ALKS` prefix. + +**Implementation Areas:** +- `src/lib/getKeyOutput.ts` - Add changeNumber to terraformarg output generation + +**Acceptance Criteria:** +- **AC-3.1:** Given a user runs `alks sessions open --ciid 123 -o terraformarg`, When a ChangeMinder ticket is successfully created, Then the changeNumber is included in Terraform argument format with ALKS prefix (e.g., `-var alks_change_number=CHG123456`) +- **AC-3.2:** Given a user runs `alks sessions open -o terraformarg` without ChangeMinder flags, When credentials are generated, Then the output remains unchanged from current behavior + +--- + +### FR-4: Output changeNumber in `terraformenv` format +**Priority:** Must Have +**Assigned to Goal:** Primary Goal + +When ChangeMinder flags are provided and a change ticket is successfully created, include `changeNumber` in Terraform environment variable format with `ALKS` prefix. + +**Implementation Areas:** +- `src/lib/getKeyOutput.ts` - Add changeNumber to terraformenv output generation + +**Acceptance Criteria:** +- **AC-4.1:** Given a user runs `alks sessions open --ciid 123 -o terraformenv`, When a ChangeMinder ticket is successfully created, Then the changeNumber is included as an ALKS-prefixed environment variable (e.g., `ALKS_CHANGE_NUMBER=CHG123456`) +- **AC-4.2:** Given a user runs `alks sessions open -o terraformenv` without ChangeMinder flags, When credentials are generated, Then the output remains unchanged from current behavior + +--- + +### FR-5: Output changeNumber in `aws` format +**Priority:** Must Have +**Assigned to Goal:** Primary Goal + +When ChangeMinder flags are provided and a change ticket is successfully created, include `changeNumber` in AWS CLI credential process format. + +**Implementation Areas:** +- `src/lib/getKeyOutput.ts` - Add changeNumber to aws output generation + +**Acceptance Criteria:** +- **AC-5.1:** Given a user runs `alks sessions open --ciid 123 -o aws`, When a ChangeMinder ticket is successfully created, Then the changeNumber is included in the AWS credential process JSON output +- **AC-5.2:** Given a user runs `alks sessions open -o aws` without ChangeMinder flags, When credentials are generated, Then the output remains unchanged from current behavior + +--- + +### FR-6: Output changeNumber in `idea` format +**Priority:** Must Have +**Assigned to Goal:** Primary Goal + +When ChangeMinder flags are provided and a change ticket is successfully created, include `changeNumber` in IntelliJ IDEA environment variable format. + +**Implementation Areas:** +- `src/lib/getKeyOutput.ts` - Add changeNumber to idea output generation + +**Acceptance Criteria:** +- **AC-6.1:** Given a user runs `alks sessions open --ciid 123 -o idea`, When a ChangeMinder ticket is successfully created, Then the changeNumber is included in IntelliJ IDEA environment variable format +- **AC-6.2:** Given a user runs `alks sessions open -o idea` without ChangeMinder flags, When credentials are generated, Then the output remains unchanged from current behavior + +--- + +## Out of Scope / Non-Goals + +1. **ChangeMinder Backend Flakiness** - Errors like "Error obtaining Change Request Number" are backend issues and will not be addressed in this feature +2. **Session Cache ChangeMinder Bug** - The issue where session cache doesn't forward ChangeMinder params when the `-N` flag is omitted is a separate bug and out of scope for this feature +3. **ChangeMinder API Integration Changes** - No modifications to the ChangeMinder client or API integration are in scope; only output formatting is affected +4. **Existing Output Behavior Changes** - When ChangeMinder flags are not provided, existing output behavior must remain completely unchanged + +--- + +## Constraints + +1. **Backward Compatibility:** Existing behavior for each output format must remain unchanged when ChangeMinder flags are not provided +2. **Format-Specific Conventions:** Each output format has its own key/value conventions that must be followed (e.g., Docker uses `-e`, Terraform uses `-var`, etc.) +3. **Conditional Output:** changeNumber should only be included in output when: + - ChangeMinder flags are provided by the user AND + - A change ticket is successfully created (no placeholder or error message if creation fails) + +--- + +## Assumptions + +1. The changeNumber value is already available in the session data when ChangeMinder ticket creation succeeds +2. Each output format's existing implementation can be extended without breaking changes +3. Unit test infrastructure is already in place for testing output formats +4. Santiago Sandoval and Nick Gibson can be reached via #alks-support Slack channel for notification + +--- + +## Dependencies + +**Internal Dependencies:** +- Existing ChangeMinder integration in ALKS CLI (no changes required) +- `src/lib/getKeyOutput.ts` - Main output formatting module +- `src/lib/updateCreds.ts` - AWS credentials file writing module + +**External Dependencies:** +- None (no external system changes required) + +--- + +## Non-Functional Requirements + +### Maintainability +- **NFR-1:** All changes must include unit tests covering changeNumber inclusion in each modified output format +- **NFR-2:** Code changes should follow existing ALKS CLI patterns and conventions (TypeScript, prettier, tslint) + +### Compatibility +- **NFR-3:** Maintain 100% backward compatibility - existing output behavior unchanged when ChangeMinder flags are not provided +- **NFR-4:** No performance impact - adding output fields should have negligible overhead + +### Security +- **NFR-5:** Change ticket numbers are not sensitive data and can be included in output following existing security posture for credential outputs + +--- + +## Success Metrics + +1. **Primary Success Criterion:** Santiago Sandoval's DLT team can successfully capture changeNumber in scripted DLQ replay workflows using `-o creds` format +2. **Quality Metric:** All unit tests pass for all six modified output formats with no regressions in existing behavior +3. **Completion Metric:** Santiago Sandoval and Nick Gibson are notified via #alks-support Slack thread that the fix is complete + +--- + +## Open Questions + +*None at this time* + +--- + +## Notes + +- This specification was generated from Rally Feature F281658 +- Primary stakeholder: Santiago Sandoval (DLT team) +- Secondary stakeholder: Nick Gibson +- Slack thread: #alks-support diff --git a/spec-driven/changeminder-output-formats/tasks.md b/spec-driven/changeminder-output-formats/tasks.md new file mode 100644 index 00000000..2ff1d3bf --- /dev/null +++ b/spec-driven/changeminder-output-formats/tasks.md @@ -0,0 +1,318 @@ +# Task Bundle: Add ChangeMinder Change Number to All Output Formats + +**feature:** Add ChangeMinder Change Number to All Output Formats +**plan_source:** spec-driven/changeminder-output-formats/plan.md +**spec_source:** spec-driven/changeminder-output-formats/spec.md +**plan_hash:** 6b127624ec2e606a84aafe5395d8426d576bf896cf0bd97f910ded547f41a8ce +**spec_hash:** a1d08bea375f8daabbc61ece3714c7bd2c92a0a7f67660f927ed4744546eb8aa +**version:** 1.0 +**status:** final +**bundle_mode:** sequential +**total_tasks:** 7 +**total_bundles:** 2 +**validation:** subagent +**created:** 2026-03-23 + +--- + +## Overview + +This task bundle implements changeNumber output for six ALKS CLI output formats that currently discard the change ticket number: `creds`, `docker`, `terraformarg`, `terraformenv`, `aws`, and `idea`. + +**Execution Strategy:** +- **Bundle 1 (Phase 1 - Walking Skeleton):** Sequential execution to prove the creds format pattern end-to-end +- **Bundle 2 (Phase 2 - Remaining Formats):** Parallel execution supported (independent switch case modifications) + +**Test Approach:** TDD with Jest — tests written before implementation for all behavioral logic + +--- + +## Traceability + +| FR | AC | STEP | Tasks | +|----|----|----|-------| +| FR-1 | AC-1.1, AC-1.2 | STEP-1 | TASK-1 | +| FR-1 | AC-1.1 | STEP-2 | TASK-2 | +| FR-2 | AC-2.1, AC-2.2 | STEP-3 | TASK-3 | +| FR-3 | AC-3.1, AC-3.2 | STEP-4 | TASK-4 | +| FR-4 | AC-4.1, AC-4.2 | STEP-5 | TASK-5 | +| FR-5 | AC-5.1, AC-5.2 | STEP-6 | TASK-6 | +| FR-6 | AC-6.1, AC-6.2 | STEP-7 | TASK-7 | + +--- + +## Bundle 1: Walking Skeleton - Prove creds Format Pattern + +> Phase: Walking Skeleton | Parallel: no | Files: src/lib/getKeyOutput.ts, src/lib/updateCreds.ts + +**Goal:** Prove the changeNumber output pattern end-to-end with the most complex format (creds), which touches both output formatting and file writing. + +### TASK-1: Implement changeNumber in creds output format +**Trace:** STEP-1 → FR-1 → AC-1.1, AC-1.2 +**Files:** src/lib/getKeyOutput.ts, src/lib/getKeyOutput.test.ts +**Effort:** M + +> **Intent:** The creds format returns empty string (no console output) since credentials are written to file by updateCreds.ts. However, getKeyOutput must still construct the Key object with changeNumber populated so downstream consumers (including updateCreds.ts) receive it. Missing this will cause updateCreds to receive undefined changeNumber even when the ticket was created. + +**Sub-steps:** +1. Create `src/lib/getKeyOutput.test.ts` if it doesn't exist (follow pattern from existing `.test.ts` files like `src/lib/getIamKey.test.ts`) +2. Write failing tests for creds format changeNumber handling: + - Test: When format is `creds` and changeNumber is defined, verify returned data structure includes changeNumber (AC-1.1) + - Test: When format is `creds` and changeNumber is undefined, verify output unchanged from baseline (AC-1.2) +3. Modify `src/lib/getKeyOutput.ts` in the `creds` case: + - Ensure the Key object or returned structure includes `changeNumber` when `key.changeNumber` is defined + - Reference existing changeNumber handling in `json` or `powershell` formats for the conditional pattern (`if (key.changeNumber) { ... }`) +4. Run tests and verify they pass +5. Type-check with `tsc --noEmit` + +**Verify:** +- `npm test -- --testPathPattern=getKeyOutput` (tests pass) +- Given a Key object with changeNumber='CHG123', when format='creds', then the output includes changeNumber='CHG123' +- Given a Key object without changeNumber, when format='creds', then the output matches baseline behavior +- `tsc --noEmit` (no type errors) + +> Depends on: — | Enables: TASK-2 | Parallel with: — + +--- + +### TASK-2: Write changeNumber to AWS credentials file +**Trace:** STEP-2 → FR-1 → AC-1.1 +**Files:** src/lib/updateCreds.ts, src/lib/updateCreds.test.ts +**Effort:** M + +> **Intent:** AWS credentials files use INI format with `[profile-name]` sections. changeNumber must be written as a comment line (e.g., `# ALKS_CHANGE_NUMBER=CHG123456`) immediately above the profile section to preserve INI spec compliance. Writing it as a key-value pair within the section will cause AWS CLI to fail parsing unrecognized fields. + +**Sub-steps:** +1. Create `src/lib/updateCreds.test.ts` if it doesn't exist (follow pattern from `src/lib/getAwsCredentialsFile.test.ts`) +2. Write failing tests for credentials file changeNumber writing: + - Test: When changeNumber is present, verify it's written as `# ALKS_CHANGE_NUMBER=` comment above profile section (AC-1.1) + - Test: When changeNumber is absent, verify credentials file format unchanged + - Test: Verify comment line is valid INI syntax (can be parsed by AWS CLI) +3. Read `src/lib/updateCreds.ts` to understand propIni usage +4. Modify `src/lib/updateCreds.ts`: + - Before calling `propIni.addData` for the profile section, check if `key.changeNumber` is defined + - If defined, manually insert comment line `# ALKS_CHANGE_NUMBER=${key.changeNumber}` into the file content + - Reference `src/lib/awsCredentialsFileContstants.ts` if needed for field names +5. Run tests and verify they pass + +**Verify:** +- `npm test -- --testPathPattern=updateCreds` (tests pass) +- Integration test: `alks sessions open --ciid 123 -o creds` creates credentials file with `# ALKS_CHANGE_NUMBER=CHG...` comment above profile section +- Given a Key with changeNumber, when updateCreds writes to file, then the comment `# ALKS_CHANGE_NUMBER=` appears immediately before the profile section +- `tsc --noEmit` (no type errors) + +> Depends on: TASK-1 | Enables: — | Parallel with: — + +--- + +## Bundle 2: Incremental Depth - Remaining Formats [P] + +> Phase: Incremental Depth | Parallel: yes | Files: src/lib/getKeyOutput.ts + +**Goal:** Extend changeNumber output to the remaining five formats (docker, terraformarg, terraformenv, aws, idea), following the pattern proven in Bundle 1. + +**Parallelization:** All tasks in this bundle modify different switch cases in getKeyOutput.ts and are file-disjoint logically — safe for parallel execution. + +### TASK-3: Implement changeNumber in docker output format +**Trace:** STEP-3 → FR-2 → AC-2.1, AC-2.2 +**Files:** src/lib/getKeyOutput.ts, src/lib/getKeyOutput.test.ts +**Effort:** S + +> **Intent:** Docker format outputs environment variables as `-e KEY=value` arguments for `docker run`. changeNumber must use the exact variable name `ALKS_CHANGE_NUMBER` (not `CHANGE_NUMBER`) to maintain consistency with existing ALKS-prefixed variables in terraform formats and avoid collision with user-defined container environment variables. + +**Sub-steps:** +1. Write failing tests in `src/lib/getKeyOutput.test.ts` for docker format: + - Test: When format is `docker` and changeNumber is defined, verify output includes `-e ALKS_CHANGE_NUMBER=` (AC-2.1) + - Test: When format is `docker` and changeNumber is undefined, verify output unchanged (AC-2.2) +2. Modify `src/lib/getKeyOutput.ts` in the `docker` case: + - Append `-e ALKS_CHANGE_NUMBER=${key.changeNumber}` to the output string when `key.changeNumber` is defined + - Follow spacing/formatting pattern from existing AWS credential variables in docker output +3. Run tests and verify they pass + +**Verify:** +- `npm test -- --testPathPattern=getKeyOutput` (docker format tests pass) +- Given a Key with changeNumber='CHG123', when format='docker', then output includes `-e ALKS_CHANGE_NUMBER=CHG123` +- `tsc --noEmit` (no type errors) + +> Depends on: — | Enables: — | Parallel with: TASK-4, TASK-5, TASK-6, TASK-7 + +--- + +### TASK-4: Implement changeNumber in terraformarg output format +**Trace:** STEP-4 → FR-3 → AC-3.1, AC-3.2 +**Files:** src/lib/getKeyOutput.ts, src/lib/getKeyOutput.test.ts +**Effort:** S + +> **Intent:** Terraform argument format outputs as `-var key=value` for CLI usage. The variable name `alks_change_number` uses snake_case (not camelCase or CONSTANT_CASE) to match Terraform variable naming conventions. Using CONSTANT_CASE would cause Terraform to reject the variable as invalid syntax. + +**Sub-steps:** +1. Write failing tests in `src/lib/getKeyOutput.test.ts` for terraformarg format: + - Test: When format is `terraformarg` and changeNumber is defined, verify output includes `-var alks_change_number=` (AC-3.1) + - Test: When format is `terraformarg` and changeNumber is undefined, verify output unchanged (AC-3.2) +2. Modify `src/lib/getKeyOutput.ts` in the `terraformarg` case: + - Append `-var alks_change_number=${key.changeNumber}` to output string when `key.changeNumber` is defined + - Follow existing pattern for AWS credential variables in terraformarg format (snake_case naming) +3. Run tests and verify they pass + +**Verify:** +- `npm test -- --testPathPattern=getKeyOutput` (terraformarg format tests pass) +- Given a Key with changeNumber='CHG123', when format='terraformarg', then output includes `-var alks_change_number=CHG123` +- `tsc --noEmit` (no type errors) + +> Depends on: — | Enables: — | Parallel with: TASK-3, TASK-5, TASK-6, TASK-7 + +--- + +### TASK-5: Implement changeNumber in terraformenv output format +**Trace:** STEP-5 → FR-4 → AC-4.1, AC-4.2 +**Files:** src/lib/getKeyOutput.ts, src/lib/getKeyOutput.test.ts +**Effort:** S + +> **Intent:** Terraform environment variable format outputs as `export KEY=value` for shell sourcing. The variable name `ALKS_CHANGE_NUMBER` uses CONSTANT_CASE (not snake_case) to match shell environment variable conventions and distinguish from Terraform's -var arguments. Shell variables are case-sensitive — mixing cases will create separate variables. + +**Sub-steps:** +1. Write failing tests in `src/lib/getKeyOutput.test.ts` for terraformenv format: + - Test: When format is `terraformenv` and changeNumber is defined, verify output includes `export ALKS_CHANGE_NUMBER=` or platform-specific equivalent (AC-4.1) + - Test: When format is `terraformenv` and changeNumber is undefined, verify output unchanged (AC-4.2) +2. Modify `src/lib/getKeyOutput.ts` in the `terraformenv` case: + - Append `export ALKS_CHANGE_NUMBER=${key.changeNumber}` (or `SET` on Windows) when `key.changeNumber` is defined + - Reference `src/lib/isWindows.ts` if platform-specific command syntax is needed + - Follow existing pattern for AWS credential variables in terraformenv format +3. Run tests and verify they pass + +**Verify:** +- `npm test -- --testPathPattern=getKeyOutput` (terraformenv format tests pass) +- Given a Key with changeNumber='CHG123', when format='terraformenv', then output includes `export ALKS_CHANGE_NUMBER=CHG123` (or `SET ALKS_CHANGE_NUMBER=CHG123` on Windows) +- `tsc --noEmit` (no type errors) + +> Depends on: — | Enables: — | Parallel with: TASK-3, TASK-4, TASK-6, TASK-7 + +--- + +### TASK-6: Implement changeNumber in aws output format +**Trace:** STEP-6 → FR-5 → AC-5.1, AC-5.2 +**Files:** src/lib/getKeyOutput.ts, src/lib/getKeyOutput.test.ts +**Effort:** S + +> **Intent:** AWS credential process format outputs JSON per AWS CLI's external credential provider spec. changeNumber must be added as a top-level JSON property, not nested under another key. The property name should use camelCase (`changeNumber`) to match existing JSON keys like `sessionToken`, not snake_case or CONSTANT_CASE which would break naming consistency in the JSON output. + +**Sub-steps:** +1. Write failing tests in `src/lib/getKeyOutput.test.ts` for aws format: + - Test: When format is `aws` and changeNumber is defined, verify JSON output includes `"changeNumber": ""` as top-level property (AC-5.1) + - Test: When format is `aws` and changeNumber is undefined, verify JSON output unchanged (AC-5.2) +2. Modify `src/lib/getKeyOutput.ts` in the `aws` case: + - Add `changeNumber` property to the JSON object when `key.changeNumber` is defined + - Use camelCase naming to match existing keys (`accessKey`, `secretKey`, `sessionToken`) + - Ensure it's a top-level property, not nested +3. Run tests and verify they pass + +**Verify:** +- `npm test -- --testPathPattern=getKeyOutput` (aws format tests pass) +- Given a Key with changeNumber='CHG123', when format='aws', then JSON output includes `"changeNumber": "CHG123"` as top-level property +- `tsc --noEmit` (no type errors) + +> Depends on: — | Enables: — | Parallel with: TASK-3, TASK-4, TASK-5, TASK-7 + +--- + +### TASK-7: Implement changeNumber in idea output format +**Trace:** STEP-7 → FR-6 → AC-6.1, AC-6.2 +**Files:** src/lib/getKeyOutput.ts, src/lib/getKeyOutput.test.ts +**Effort:** S + +> **Intent:** IntelliJ IDEA environment variable format outputs XML elements for run configuration import. changeNumber must be added as a new `` element with name="ALKS_CHANGE_NUMBER", not as a text node or attribute on existing elements. Incorrect XML structure will cause IDEA to silently ignore the variable or fail to parse the run configuration entirely. + +**Sub-steps:** +1. Read existing `idea` format output in `src/lib/getKeyOutput.ts` to understand the current structure (XML or custom format) +2. Write failing tests in `src/lib/getKeyOutput.test.ts` for idea format: + - Test: When format is `idea` and changeNumber is defined, verify output includes ALKS_CHANGE_NUMBER in IDEA-compatible format (AC-6.1) + - Test: When format is `idea` and changeNumber is undefined, verify output unchanged (AC-6.2) +3. Modify `src/lib/getKeyOutput.ts` in the `idea` case: + - Add changeNumber to output following IDEA format conventions (likely environment variable list format) + - Follow existing pattern for AWS credential variables in idea format +4. Run tests and verify they pass + +**Verify:** +- `npm test -- --testPathPattern=getKeyOutput` (idea format tests pass) +- Given a Key with changeNumber='CHG123', when format='idea', then output includes ALKS_CHANGE_NUMBER=CHG123 in IDEA-compatible format +- `tsc --noEmit` (no type errors) + +> Depends on: — | Enables: — | Parallel with: TASK-3, TASK-4, TASK-5, TASK-6 + +--- + +## Conflict Analysis + +### Hot Files +- **src/lib/getKeyOutput.ts**: Modified by all 7 tasks + - **Bundle 1**: TASK-1 (creds case) + - **Bundle 2**: TASK-3 (docker), TASK-4 (terraformarg), TASK-5 (terraformenv), TASK-6 (aws), TASK-7 (idea) + - **Conflict Strategy**: Switch case isolation — each task modifies a different case branch, making them logically file-disjoint despite touching the same file + - **Sequencing**: Bundle 1 before Bundle 2 (walking skeleton establishes pattern). Within Bundle 2, parallel execution is safe. + +- **src/lib/updateCreds.ts**: Modified by TASK-2 only + - No conflicts + +### Test Files +- **src/lib/getKeyOutput.test.ts**: Created/modified by all 7 tasks + - Each task adds test cases for its specific format — append-only pattern minimizes conflicts + - **Bundle 2 Parallelization Risk**: Low — test cases for different formats are independent assertions + +- **src/lib/updateCreds.test.ts**: Created/modified by TASK-2 only + - No conflicts + +### Parallelization Summary +- **Bundle 1**: Sequential (TASK-1 → TASK-2 dependency) +- **Bundle 2**: Marked `[P]` — all 5 tasks can execute in parallel + - Switch case isolation in getKeyOutput.ts ensures file-disjoint modifications + - Test case append pattern in getKeyOutput.test.ts minimizes merge conflicts + +--- + +## Execution Notes + +**Sequential Mode (default):** +- Execute Bundle 1 tasks in order (TASK-1, then TASK-2) +- Execute Bundle 2 tasks sequentially (TASK-3 through TASK-7) — though they could run in parallel with agent mode + +**Agent Mode (worktree parallelism):** +- Bundle 2 tasks can execute concurrently in separate git worktrees +- Each worktree modifies a different switch case, minimizing merge conflicts +- Expected speedup: ~5x for Bundle 2 (5 tasks in parallel vs sequential) + +**Team Mode (branch/PR coordination):** +- Distribute Bundle 2 tasks across team members (one format per developer) +- Each developer creates a feature branch from Bundle 1 completion +- Merge sequence: Bundle 1 → all Bundle 2 PRs in any order + +--- + +## Closing Message Template + +After all tasks complete and the final commit is made: + +``` +✅ All 7 tasks completed successfully! + +**Implementation Summary:** +- ✅ TASK-1: creds format output includes changeNumber +- ✅ TASK-2: changeNumber written to AWS credentials file as INI comment +- ✅ TASK-3: docker format includes `-e ALKS_CHANGE_NUMBER=...` +- ✅ TASK-4: terraformarg format includes `-var alks_change_number=...` +- ✅ TASK-5: terraformenv format includes `export ALKS_CHANGE_NUMBER=...` +- ✅ TASK-6: aws format JSON includes `"changeNumber": "..."` +- ✅ TASK-7: idea format includes ALKS_CHANGE_NUMBER + +**Next Steps:** +1. Run full test suite: `npm test` +2. Manual testing: `alks sessions open --ciid 123 -o ` for each format +3. Notify Santiago Sandoval (DLT team) and Nick Gibson via #alks-support Slack thread +4. Merge to master and release + +**Verification Commands:** +- `npm test -- --coverage` (verify >80% coverage for modified functions) +- `npm run tslint` (code style compliance) +- Integration test each format with real ChangeMinder ticket creation + +Santiago's DLT team can now capture change ticket numbers in scripted DLQ replay workflows! 🎉 +``` diff --git a/src/lib/getKeyOutput.test.ts b/src/lib/getKeyOutput.test.ts new file mode 100644 index 00000000..41adb8d6 --- /dev/null +++ b/src/lib/getKeyOutput.test.ts @@ -0,0 +1,245 @@ +import { Key } from '../model/keys'; +import { getKeyOutput } from './getKeyOutput'; +import { updateCreds } from './updateCreds'; + +jest.mock('./updateCreds'); +jest.mock('./log'); +jest.mock('./getStdErrPrompt'); +jest.mock('./getPrompt'); +jest.mock('inquirer-autocomplete-prompt', () => ({}), { virtual: true }); + +// Silence console.error +jest.spyOn(global.console, 'error').mockImplementation(() => {}); + +describe('getKeyOutput', () => { + const mockKey: Key = { + accessKey: 'AKIAIOSFODNN7EXAMPLE', + secretKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + sessionToken: 'AQoDYXdzEJr......EXAMPLE', + alksAccount: '012345678910/ALKSAdmin - awstest123', + alksRole: 'Admin', + isIAM: false, + expires: new Date('2026-03-24T00:00:00Z'), + }; + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('creds format', () => { + it('should pass key with changeNumber to updateCreds when changeNumber is defined (AC-1.1)', () => { + const keyWithChangeNumber: Key = { + ...mockKey, + changeNumber: 'CHG123456', + }; + const mockUpdateCreds = updateCreds as jest.MockedFunction< + typeof updateCreds + >; + mockUpdateCreds.mockReturnValue(true); + + getKeyOutput('creds', keyWithChangeNumber, undefined, false); + + // Verify updateCreds was called with the key containing changeNumber + expect(mockUpdateCreds).toHaveBeenCalledWith( + expect.objectContaining({ + changeNumber: 'CHG123456', + }), + undefined, + false + ); + }); + + it('should pass key without changeNumber to updateCreds when changeNumber is undefined (AC-1.2)', () => { + const keyWithoutChangeNumber: Key = { + ...mockKey, + // changeNumber is undefined + }; + const mockUpdateCreds = updateCreds as jest.MockedFunction< + typeof updateCreds + >; + mockUpdateCreds.mockReturnValue(true); + + getKeyOutput('creds', keyWithoutChangeNumber, undefined, false); + + // Verify updateCreds was called with the key (changeNumber will be undefined) + expect(mockUpdateCreds).toHaveBeenCalledWith( + expect.objectContaining({ + accessKey: keyWithoutChangeNumber.accessKey, + }), + undefined, + false + ); + + // Verify changeNumber is not set (undefined) + const callArg = mockUpdateCreds.mock.calls[0][0] as Key; + expect(callArg.changeNumber).toBeUndefined(); + }); + + it('should return success message when updateCreds succeeds', () => { + const mockUpdateCreds = updateCreds as jest.MockedFunction< + typeof updateCreds + >; + mockUpdateCreds.mockReturnValue(true); + + const result = getKeyOutput('creds', mockKey, undefined, false); + + expect(result).toBe('Your AWS credentials file has been updated'); + }); + + it('should return success message with profile name when profile is specified', () => { + const mockUpdateCreds = updateCreds as jest.MockedFunction< + typeof updateCreds + >; + mockUpdateCreds.mockReturnValue(true); + + const result = getKeyOutput('creds', mockKey, 'myprofile', false); + + expect(result).toBe( + 'Your AWS credentials file has been updated with the named profile: myprofile' + ); + }); + }); + + describe('docker format', () => { + it('should include changeNumber when defined (AC-2.1)', () => { + const keyWithChangeNumber: Key = { + ...mockKey, + changeNumber: 'CHG123456', + }; + + const result = getKeyOutput( + 'docker', + keyWithChangeNumber, + undefined, + false + ); + + expect(result).toContain('-e CHANGE_NUMBER=CHG123456'); + }); + + it('should not include changeNumber when undefined (AC-2.2)', () => { + const result = getKeyOutput('docker', mockKey, undefined, false); + + expect(result).not.toContain('CHANGE_NUMBER'); + }); + }); + + describe('terraformarg format', () => { + it('should include changeNumber when defined (AC-3.1)', () => { + const keyWithChangeNumber: Key = { + ...mockKey, + changeNumber: 'CHG123456', + }; + + const result = getKeyOutput( + 'terraformarg', + keyWithChangeNumber, + undefined, + false + ); + + expect(result).toContain('-e CHANGE_NUMBER=CHG123456'); + }); + + it('should not include changeNumber when undefined (AC-3.2)', () => { + const result = getKeyOutput('terraformarg', mockKey, undefined, false); + + expect(result).not.toContain('CHANGE_NUMBER'); + }); + }); + + describe('terraformenv format', () => { + it('should include changeNumber when defined (AC-4.1)', () => { + const keyWithChangeNumber: Key = { + ...mockKey, + changeNumber: 'CHG123456', + }; + + const result = getKeyOutput( + 'tarraformenv', + keyWithChangeNumber, + undefined, + false + ); + + expect(result).toMatch(/CHANGE_NUMBER=CHG123456/); + }); + + it('should not include changeNumber when undefined (AC-4.2)', () => { + const result = getKeyOutput('tarraformenv', mockKey, undefined, false); + + expect(result).not.toContain('CHANGE_NUMBER'); + }); + }); + + describe('aws format', () => { + it('should include changeNumber when defined (AC-5.1)', () => { + const keyWithChangeNumber: Key = { + ...mockKey, + changeNumber: 'CHG123456', + }; + + const result = getKeyOutput('aws', keyWithChangeNumber, undefined, false); + const parsed = JSON.parse(result); + + expect(parsed.ChangeNumber).toBe('CHG123456'); + }); + + it('should not include changeNumber when undefined (AC-5.2)', () => { + const result = getKeyOutput('aws', mockKey, undefined, false); + const parsed = JSON.parse(result); + + expect(parsed.ChangeNumber).toBeUndefined(); + }); + }); + + describe('idea format', () => { + it('should include changeNumber when defined (AC-6.1)', () => { + const keyWithChangeNumber: Key = { + ...mockKey, + changeNumber: 'CHG123456', + }; + + const result = getKeyOutput( + 'idea', + keyWithChangeNumber, + undefined, + false + ); + + expect(result).toContain('CHANGE_NUMBER=CHG123456'); + }); + + it('should not include changeNumber when undefined (AC-6.2)', () => { + const result = getKeyOutput('idea', mockKey, undefined, false); + + expect(result).not.toContain('CHANGE_NUMBER'); + }); + }); + + describe('other formats', () => { + it('should include changeNumber in json format', () => { + const keyWithChangeNumber: Key = { + ...mockKey, + changeNumber: 'CHG123456', + }; + + const result = getKeyOutput( + 'json', + keyWithChangeNumber, + undefined, + false + ); + const parsed = JSON.parse(result); + + expect(parsed.changeNumber).toBe('CHG123456'); + }); + + it('should handle undefined changeNumber in json format', () => { + const result = getKeyOutput('json', mockKey, undefined, false); + const parsed = JSON.parse(result); + + expect(parsed.changeNumber).toBeUndefined(); + }); + }); +}); diff --git a/src/lib/getKeyOutput.ts b/src/lib/getKeyOutput.ts index 3881041e..5b2ed9e7 100644 --- a/src/lib/getKeyOutput.ts +++ b/src/lib/getKeyOutput.ts @@ -18,14 +18,26 @@ export function getKeyOutput( switch (format) { case 'docker': { - return `-e AWS_ACCESS_KEY_ID=${key.accessKey} -e AWS_SECRET_ACCESS_KEY=${key.secretKey} -e AWS_SESSION_TOKEN=${key.sessionToken} -e AWS_SESSION_EXPIRES=${keyExpires}`; + let output = `-e AWS_ACCESS_KEY_ID=${key.accessKey} -e AWS_SECRET_ACCESS_KEY=${key.secretKey} -e AWS_SESSION_TOKEN=${key.sessionToken} -e AWS_SESSION_EXPIRES=${keyExpires}`; + if (key.changeNumber) { + output += ` -e CHANGE_NUMBER=${key.changeNumber}`; + } + return output; } case 'terraformarg': { - return `-e ALKS_ACCESS_KEY_ID=${key.accessKey} -e ALKS_SECRET_ACCESS_KEY=${key.secretKey} -e ALKS_SESSION_TOKEN=${key.sessionToken} -e ALKS_SESSION_EXPIRES=${keyExpires}`; + let output = `-e ALKS_ACCESS_KEY_ID=${key.accessKey} -e ALKS_SECRET_ACCESS_KEY=${key.secretKey} -e ALKS_SESSION_TOKEN=${key.sessionToken} -e ALKS_SESSION_EXPIRES=${keyExpires}`; + if (key.changeNumber) { + output += ` -e CHANGE_NUMBER=${key.changeNumber}`; + } + return output; } case 'tarraformenv': { const cmd = isWindows() ? 'SET' : 'export'; - return `${cmd} ALKS_ACCESS_KEY_ID=${key.accessKey} && ${cmd} ALKS_SECRET_ACCESS_KEY=${key.secretKey} && ${cmd} ALKS_SESSION_TOKEN=${key.sessionToken} && ${cmd} ALKS_SESSION_EXPIRES=${keyExpires}`; + let output = `${cmd} ALKS_ACCESS_KEY_ID=${key.accessKey} && ${cmd} ALKS_SECRET_ACCESS_KEY=${key.secretKey} && ${cmd} ALKS_SESSION_TOKEN=${key.sessionToken} && ${cmd} ALKS_SESSION_EXPIRES=${keyExpires}`; + if (key.changeNumber) { + output += ` && ${cmd} CHANGE_NUMBER=${key.changeNumber}`; + } + return output; } case 'json': { const keyData = { @@ -53,7 +65,11 @@ export function getKeyOutput( } } case 'idea': { - return `AWS_ACCESS_KEY_ID=${key.accessKey}\nAWS_SECRET_ACCESS_KEY=${key.secretKey}\nAWS_SESSION_TOKEN=${key.sessionToken}\nAWS_SESSION_EXPIRES=${keyExpires}`; + let output = `AWS_ACCESS_KEY_ID=${key.accessKey}\nAWS_SECRET_ACCESS_KEY=${key.secretKey}\nAWS_SESSION_TOKEN=${key.sessionToken}\nAWS_SESSION_EXPIRES=${keyExpires}`; + if (key.changeNumber) { + output += `\nCHANGE_NUMBER=${key.changeNumber}`; + } + return output; } case 'powershell': { return `$env:AWS_ACCESS_KEY_ID, $env:AWS_SECRET_ACCESS_KEY, $env:AWS_SESSION_TOKEN, $env:AWS_SESSION_EXPIRES, $env:CHANGE_NUMBER = "${ @@ -66,13 +82,17 @@ export function getKeyOutput( return `set -xg AWS_ACCESS_KEY_ID '${key.accessKey}'; and set -xg AWS_SECRET_ACCESS_KEY '${key.secretKey}'; and set -xg AWS_SESSION_TOKEN '${key.sessionToken}'; and set -xg AWS_SESSION_EXPIRES '${keyExpires}'; and set -xg CHANGE_NUMBER '${key.changeNumber}'`; } case 'aws': { - return JSON.stringify({ + const awsOutput: any = { Version: 1, AccessKeyId: key.accessKey, SecretAccessKey: key.secretKey, SessionToken: key.sessionToken, Expiration: moment(key.expires).toISOString(), - }); + }; + if (key.changeNumber) { + awsOutput.ChangeNumber = key.changeNumber; + } + return JSON.stringify(awsOutput); } case 'linux': { // forces export format diff --git a/src/lib/updateCreds.test.ts b/src/lib/updateCreds.test.ts new file mode 100644 index 00000000..9d6cd4f3 --- /dev/null +++ b/src/lib/updateCreds.test.ts @@ -0,0 +1,159 @@ +import { readFileSync, writeFileSync } from 'fs'; +import { Key } from '../model/keys'; +import { updateCreds } from './updateCreds'; +import { createInstance } from 'prop-ini'; +import { getAwsCredentialsFile } from './getAwsCredentialsFile'; + +jest.mock('fs', () => ({ + ...jest.requireActual('fs'), + promises: { + mkdir: jest.fn(), + appendFile: jest.fn(), + stat: jest.fn(), + unlink: jest.fn(), + }, + readFileSync: jest.fn(), + writeFileSync: jest.fn(), +})); +jest.mock('prop-ini'); +jest.mock('./getAwsCredentialsFile'); +jest.mock('./addNewLineToEof'); +jest.mock('./log'); +jest.mock('inquirer-autocomplete-prompt', () => ({}), { virtual: true }); + +describe('updateCreds', () => { + const mockCredFile = '/home/user/.aws/credentials'; + const mockKey: Key = { + accessKey: 'AKIAIOSFODNN7EXAMPLE', + secretKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + sessionToken: 'AQoDYXdzEJr......EXAMPLE', + alksAccount: '012345678910/ALKSAdmin - awstest123', + alksRole: 'Admin', + isIAM: false, + expires: new Date('2026-03-24T00:00:00Z'), + }; + + const mockPropIni = { + decode: jest.fn(), + addData: jest.fn(), + removeData: jest.fn(), + encode: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + (getAwsCredentialsFile as jest.Mock).mockReturnValue(mockCredFile); + (createInstance as jest.Mock).mockReturnValue(mockPropIni); + mockPropIni.decode.mockReturnValue({ sections: {} }); + (readFileSync as jest.Mock).mockReturnValue(''); + }); + + describe('changeNumber handling', () => { + it('should write changeNumber as a comment above the profile section when present (AC-1.1)', () => { + const keyWithChangeNumber: Key = { + ...mockKey, + changeNumber: 'CHG123456', + }; + + // Mock initial credentials file content (empty) + (readFileSync as jest.Mock).mockReturnValue(''); + + // Mock the encoded content after propIni.encode() + const encodedContent = `[default] +aws_access_key_id = ${mockKey.accessKey} +aws_secret_access_key = ${mockKey.secretKey} +aws_session_token = ${mockKey.sessionToken} +alks_managed_by = alks +`; + mockPropIni.encode.mockImplementation(() => { + (readFileSync as jest.Mock).mockReturnValue(encodedContent); + }); + + const result = updateCreds(keyWithChangeNumber, undefined, false); + + expect(result).toBe(true); + + // Verify that writeFileSync was called with content containing the comment + expect(writeFileSync).toHaveBeenCalled(); + const writeCall = (writeFileSync as jest.Mock).mock.calls.find( + (call) => call[0] === mockCredFile && typeof call[1] === 'string' + ); + + if (writeCall) { + const writtenContent = writeCall[1]; + expect(writtenContent).toContain('# CHANGE_NUMBER=CHG123456'); + expect(writtenContent).toContain('[default]'); + // Verify the comment appears before the profile section + const commentIndex = writtenContent.indexOf( + '# CHANGE_NUMBER=CHG123456' + ); + const sectionIndex = writtenContent.indexOf('[default]'); + expect(commentIndex).toBeGreaterThan(-1); + expect(sectionIndex).toBeGreaterThan(-1); + expect(commentIndex).toBeLessThan(sectionIndex); + } + }); + + it('should not write changeNumber comment when changeNumber is undefined (AC-1.2)', () => { + const keyWithoutChangeNumber: Key = { + ...mockKey, + // changeNumber is undefined + }; + + const encodedContent = `[default] +aws_access_key_id = ${mockKey.accessKey} +aws_secret_access_key = ${mockKey.secretKey} +aws_session_token = ${mockKey.sessionToken} +alks_managed_by = alks +`; + mockPropIni.encode.mockImplementation(() => { + (readFileSync as jest.Mock).mockReturnValue(encodedContent); + }); + + const result = updateCreds(keyWithoutChangeNumber, undefined, false); + + expect(result).toBe(true); + + // If writeFileSync was called, verify no ALKS_CHANGE_NUMBER comment is present + const writeCall = (writeFileSync as jest.Mock).mock.calls.find( + (call) => call[0] === mockCredFile && typeof call[1] === 'string' + ); + + if (writeCall) { + const writtenContent = writeCall[1]; + expect(writtenContent).not.toContain('CHANGE_NUMBER'); + } + }); + + it('should write changeNumber comment for named profile', () => { + const keyWithChangeNumber: Key = { + ...mockKey, + changeNumber: 'CHG789012', + }; + + const encodedContent = `[myprofile] +aws_access_key_id = ${mockKey.accessKey} +aws_secret_access_key = ${mockKey.secretKey} +aws_session_token = ${mockKey.sessionToken} +alks_managed_by = alks +`; + mockPropIni.encode.mockImplementation(() => { + (readFileSync as jest.Mock).mockReturnValue(encodedContent); + }); + + const result = updateCreds(keyWithChangeNumber, 'myprofile', false); + + expect(result).toBe(true); + + const writeCall = (writeFileSync as jest.Mock).mock.calls.find( + (call) => call[0] === mockCredFile && typeof call[1] === 'string' + ); + + if (writeCall) { + const writtenContent = writeCall[1]; + expect(writtenContent).toContain('# CHANGE_NUMBER=CHG789012'); + expect(writtenContent).toContain('[myprofile]'); + } + }); + }); +}); diff --git a/src/lib/updateCreds.ts b/src/lib/updateCreds.ts index 46d08176..32693de7 100644 --- a/src/lib/updateCreds.ts +++ b/src/lib/updateCreds.ts @@ -1,6 +1,7 @@ -import { AwsKey } from '../model/keys'; +import { Key } from '../model/keys'; import { createInstance } from 'prop-ini'; import { has } from 'underscore'; +import { readFileSync, writeFileSync } from 'fs'; import { addNewLineToEof } from './addNewLineToEof'; import { getAwsCredentialsFile } from './getAwsCredentialsFile'; import { @@ -12,7 +13,7 @@ import { } from './awsCredentialsFileContstants'; export function updateCreds( - key: AwsKey, + key: Key, profile: string | undefined, force: boolean = false ) { @@ -47,6 +48,24 @@ export function updateCreds( propIni.encode({ file: credFile }); + // Write changeNumber as a comment above the profile section if present + if (key.changeNumber) { + const fileContent = readFileSync(credFile, 'utf-8'); + const sectionHeader = `[${section}]`; + const commentLine = `# CHANGE_NUMBER=${key.changeNumber}`; + + // Find the section header and insert the comment before it + const sectionIndex = fileContent.indexOf(sectionHeader); + if (sectionIndex !== -1) { + const modifiedContent = + fileContent.slice(0, sectionIndex) + + commentLine + + '\n' + + fileContent.slice(sectionIndex); + writeFileSync(credFile, modifiedContent, 'utf-8'); + } + } + // propIni doesnt add a new line, so running aws configure will cause issues addNewLineToEof(credFile);